@@ -136,51 +136,44 @@ function moveHistory(changeType) {
136
136
}
137
137
138
138
function unwrapIfNotMulti ( paths , idProps , spec , anyVals , depType ) {
139
+ let msg = '' ;
140
+
139
141
if ( isMultiValued ( spec ) ) {
140
- return idProps ;
142
+ return [ idProps , msg ] ;
141
143
}
144
+
142
145
if ( idProps . length !== 1 ) {
143
146
if ( ! idProps . length ) {
144
- if ( typeof spec . id === 'string' ) {
145
- throw new ReferenceError (
146
- 'A nonexistent object was used in an `' +
147
- depType +
148
- '` of a Dash callback. The id of this object is `' +
149
- spec . id +
150
- '` and the property is `' +
151
- spec . property +
152
- '`. The string ids in the current layout are: [' +
153
- keys ( paths . strs ) . join ( ', ' ) +
154
- ']'
155
- ) ;
156
- }
157
- // TODO: unwrapped list of wildcard ids?
158
- // eslint-disable-next-line no-console
159
- console . log ( paths . objs ) ;
160
- throw new ReferenceError (
147
+ const isStr = typeof spec . id === 'string' ;
148
+ msg =
161
149
'A nonexistent object was used in an `' +
162
- depType +
163
- '` of a Dash callback. The id of this object is ' +
164
- JSON . stringify ( spec . id ) +
165
- ( anyVals ? ' with MATCH values ' + anyVals : '' ) +
166
- ' and the property is `' +
167
- spec . property +
168
- '`. The wildcard ids currently available are logged above.'
169
- ) ;
170
- }
171
- throw new ReferenceError (
172
- 'Multiple objects were found for an `' +
150
+ depType +
151
+ '` of a Dash callback. The id of this object is ' +
152
+ ( isStr
153
+ ? '`' + spec . id + '`'
154
+ : JSON . stringify ( spec . id ) +
155
+ ( anyVals ? ' with MATCH values ' + anyVals : '' ) ) +
156
+ ' and the property is `' +
157
+ spec . property +
158
+ ( isStr
159
+ ? '`. The string ids in the current layout are: [' +
160
+ keys ( paths . strs ) . join ( ', ' ) +
161
+ ']'
162
+ : '`. The wildcard ids currently available are logged above.' ) ;
163
+ } else {
164
+ msg =
165
+ 'Multiple objects were found for an `' +
173
166
depType +
174
167
'` of a callback that only takes one value. The id spec is ' +
175
168
JSON . stringify ( spec . id ) +
176
169
( anyVals ? ' with MATCH values ' + anyVals : '' ) +
177
170
' and the property is `' +
178
171
spec . property +
179
172
'`. The objects we found are: ' +
180
- JSON . stringify ( map ( pick ( [ 'id' , 'property' ] ) , idProps ) )
181
- ) ;
173
+ JSON . stringify ( map ( pick ( [ 'id' , 'property' ] ) , idProps ) ) ;
174
+ }
182
175
}
183
- return idProps [ 0 ] ;
176
+ return [ idProps [ 0 ] , msg ] ;
184
177
}
185
178
186
179
function startCallbacks ( callbacks ) {
@@ -247,20 +240,51 @@ async function fireReadyCallbacks(dispatch, getState, callbacks) {
247
240
248
241
let payload ;
249
242
try {
250
- const outputs = allOutputs . map ( ( out , i ) =>
251
- unwrapIfNotMulti (
243
+ const inVals = fillVals ( paths , layout , cb , inputs , 'Input' , true ) ;
244
+
245
+ const preventCallback = ( ) => {
246
+ removeCallbackFromPending ( ) ;
247
+ // no server call here; for performance purposes pretend this is
248
+ // a clientside callback and defer fireNext for the end
249
+ // of the currently-ready callbacks.
250
+ hasClientSide = true ;
251
+ return null ;
252
+ } ;
253
+
254
+ if ( inVals === null ) {
255
+ return preventCallback ( ) ;
256
+ }
257
+
258
+ const outputs = [ ] ;
259
+ const outputErrors = [ ] ;
260
+ allOutputs . forEach ( ( out , i ) => {
261
+ const [ outi , erri ] = unwrapIfNotMulti (
252
262
paths ,
253
263
map ( pick ( [ 'id' , 'property' ] ) , out ) ,
254
264
cb . callback . outputs [ i ] ,
255
265
cb . anyVals ,
256
266
'Output'
257
- )
258
- ) ;
267
+ ) ;
268
+ outputs . push ( outi ) ;
269
+ if ( erri ) {
270
+ outputErrors . push ( erri ) ;
271
+ }
272
+ } ) ;
273
+ if ( outputErrors . length ) {
274
+ if ( flatten ( inVals ) . length ) {
275
+ refErr ( outputErrors , paths ) ;
276
+ }
277
+ // This case is all-empty multivalued wildcard inputs,
278
+ // which we would normally fire the callback for, except
279
+ // some outputs are missing. So instead we treat it like
280
+ // regular missing inputs and just silently prevent it.
281
+ return preventCallback ( ) ;
282
+ }
259
283
260
284
payload = {
261
285
output,
262
286
outputs : isMultiOutputProp ( output ) ? outputs : outputs [ 0 ] ,
263
- inputs : fillVals ( paths , layout , cb , inputs , 'Input' ) ,
287
+ inputs : inVals ,
264
288
changedPropIds : keys ( cb . changedPropIds ) ,
265
289
} ;
266
290
if ( cb . callback . state . length ) {
@@ -360,14 +384,18 @@ async function fireReadyCallbacks(dispatch, getState, callbacks) {
360
384
updatePending ( pendingCallbacks , without ( updated , allPropIds ) ) ;
361
385
}
362
386
363
- function handleError ( err ) {
387
+ function removeCallbackFromPending ( ) {
364
388
const { pendingCallbacks} = getState ( ) ;
365
389
if ( requestIsActive ( pendingCallbacks , resolvedId , requestId ) ) {
366
390
// Skip all prop updates from this callback, and remove
367
391
// it from the pending list so callbacks it was blocking
368
392
// that have other changed inputs will still fire.
369
393
updatePending ( pendingCallbacks , allPropIds ) ;
370
394
}
395
+ }
396
+
397
+ function handleError ( err ) {
398
+ removeCallbackFromPending ( ) ;
371
399
const outputs = payload
372
400
? map ( combineIdAndProp , flatten ( [ payload . outputs ] ) ) . join ( ', ' )
373
401
: output ;
@@ -398,10 +426,13 @@ async function fireReadyCallbacks(dispatch, getState, callbacks) {
398
426
return hasClientSide ? fireNext ( ) . then ( done ) : done ;
399
427
}
400
428
401
- function fillVals ( paths , layout , cb , specs , depType ) {
429
+ function fillVals ( paths , layout , cb , specs , depType , allowAllMissing ) {
402
430
const getter = depType === 'Input' ? cb . getInputs : cb . getState ;
403
- return getter ( paths ) . map ( ( inputList , i ) =>
404
- unwrapIfNotMulti (
431
+ const errors = [ ] ;
432
+ let emptyMultiValues = 0 ;
433
+
434
+ const inputVals = getter ( paths ) . map ( ( inputList , i ) => {
435
+ const [ inputs , inputError ] = unwrapIfNotMulti (
405
436
paths ,
406
437
inputList . map ( ( { id, property, path : path_ } ) => ( {
407
438
id,
@@ -411,8 +442,45 @@ function fillVals(paths, layout, cb, specs, depType) {
411
442
specs [ i ] ,
412
443
cb . anyVals ,
413
444
depType
414
- )
415
- ) ;
445
+ ) ;
446
+ if ( isMultiValued ( specs [ i ] ) && ! inputs . length ) {
447
+ emptyMultiValues ++ ;
448
+ }
449
+ if ( inputError ) {
450
+ errors . push ( inputError ) ;
451
+ }
452
+ return inputs ;
453
+ } ) ;
454
+
455
+ if ( errors . length ) {
456
+ if (
457
+ allowAllMissing &&
458
+ errors . length + emptyMultiValues === inputVals . length
459
+ ) {
460
+ // We have at least one non-multivalued input, but all simple and
461
+ // multi-valued inputs are missing.
462
+ // (if all inputs are multivalued and all missing we still return
463
+ // them as normal, and fire the callback.)
464
+ return null ;
465
+ }
466
+ // If we get here we have some missing and some present inputs.
467
+ // Or all missing in a context that doesn't allow this.
468
+ // That's a real problem, so throw the first message as an error.
469
+ refErr ( errors , paths ) ;
470
+ }
471
+
472
+ return inputVals ;
473
+ }
474
+
475
+ function refErr ( errors , paths ) {
476
+ const err = errors [ 0 ] ;
477
+ if ( err . indexOf ( 'logged above' ) !== - 1 ) {
478
+ // Wildcard reference errors mention a list of wildcard specs logged
479
+ // TODO: unwrapped list of wildcard ids?
480
+ // eslint-disable-next-line no-console
481
+ console . error ( paths . objs ) ;
482
+ }
483
+ throw new ReferenceError ( err ) ;
416
484
}
417
485
418
486
function handleServerside ( config , payload , hooks ) {
0 commit comments