¿Cómo romper adecuadamente una cadena de promesas?

Basado en la pregunta aquí: jQuery encadenando y conectando en cascada los de entonces y cuándo y la respuesta aceptada.

Quiero romper la cadena de la promesa en un punto, pero aún no he encontrado la forma correcta. Hay varias publicaciones sobre esto, pero todavía estoy perdido.

Tomando el código de ejemplo de la pregunta original:

Menus.getCantinas().then(function(cantinas){ // `then` is how we chain promises Menus.cantinas = cantinas; // if we need to aggregate more than one promise, we `$.when` return $.when(Menus.getMeals(cantinas), Menus.getSides(cantinas)); }).then(function(meals, sides){ // in jQuery `then` can take multiple arguments Menus.sides = sides; // we can fill closure arguments here Menus.meals = meals; return Menus.getAdditives(meals, sides); // again we chain }).then(function(additives){ Menus.additives = additives; return Menus; // we can also return non promises and chain on them if we want }).done(function(){ // done terminates a chain generally. // edit HTML here }); 

¿Cómo rompería la cadena si cantinas.length == 0 ? No me gustaría recibir las comidas, ni los aditivos, francamente me gustaría llamar a algún tipo de callback de “resultado vacío”. He intentado lo siguiente, que es muy feo (pero funciona …). Enséñame el camino correcto. Este sigue siendo un resultado válido, por lo que no es un “error” per se, solo un resultado vacío, diría yo.

 var emptyResult = false; Menus.getCantinas().then(function(cantinas){ Menus.cantinas = cantinas; if (cantinas.length == 0) { emptyResult = true; return "emptyResult"; //unuglify me } return $.when(Menus.getMeals(cantinas), Menus.getSides(cantinas)); }).then(function(meals, sides){ if (meals == "emptyResult") return meals; //look at my ugliness... Menus.sides = sides; Menus.meals = meals; return Menus.getAdditives(meals, sides); }).then(function(additives){ if (additives == "emptyResult") return additives; Menus.additives = additives; return Menus; }).done(function(){ if (emptyResult) //do empty result stuff else // normal stuff }); 

En primer lugar, creo que es mejor decir que está buscando “eludir” (parte de) la cadena de la promesa en lugar de “romperla”.

Como dices, probar “emptyResult” en varios lugares es bastante feo. Afortunadamente, se dispone de un mecanismo más elegante al tiempo que se cumple el mismo principio general de no ejecutar parte de la cadena de promesas.

Un mecanismo alternativo es utilizar el rechazo de promesas para controlar el flujo, luego volver a detectar las condiciones de error específicas más adelante en la cadena y volver a colocarlo en la ruta de éxito.

 Menus.getCantinas().then(function(cantinas) { Menus.cantinas = cantinas; if(cantinas.length == 0) { return $.Deferred().reject(errMessages.noCantinas); } else { return $.when(Menus.getMeals(cantinas), Menus.getSides(cantinas)); } }).then(function(meals, sides) { Menus.sides = sides; Menus.meals = meals; return Menus.getAdditives(meals, sides); }).then(function(additives) { Menus.additives = additives; return Menus; }).then(null, function(err) { //This "catch" exists solely to detect the noCantinas condition //and put the chain back on the success path. //Any genuine error will be propagated as such. //Note: you will probably want a bit of safety here as err may not be passed and may not be a string. return (err == errMessages.noCantinas) ? $.when(Menus) : err; }).done(function(Menus) { // with no cantinas, or with everything }); var errMessages = { 'noCantinas': 'no cantinas' }; 

En el lado positivo, creo que la falta de anidamiento permite una mejor legibilidad del camino del éxito natural. Además, al menos para mí, este patrón requeriría un malabarismo mental mínimo para acomodar más desvíos, si es necesario.

En el lado negativo, este patrón es ligeramente menos eficiente que el de Bergi. Mientras que la ruta principal tiene el mismo número de promesas que Bergi, la ruta cantinas.length == 0 requiere una más (o una por bypass si se han codificado múltiples bypasses). Además, este patrón requiere una errMessages confiable de las condiciones de error específicas, de ahí el objeto errMessages , que algunos pueden encontrar detracta.

Parece que quiere ramificarse , no romperse: desea continuar como siempre hasta que se done . Una buena propiedad de las promesas es que no solo encadenan, sino que también se pueden anidar y anular sin restricciones. En su caso, puede simplemente poner la parte de la cadena que desea “romper” dentro de su if statement:

 Menus.getCantinas().then(function(cantinas) { Menus.cantinas = cantinas; if (cantinas.length == 0) return Menus; // break! // else return $.when(Menus.getMeals(cantinas), Menus.getSides(cantinas)) .then(function(meals, sides) { Menus.sides = sides; Menus.meals = meals; return Menus.getAdditives(meals, sides); }).then(function(additives) { Menus.additives = additives; return Menus; }); }).done(function(Menus) { // with no cantinas, or with everything }); 

Para las personas que usan promesas integradas del navegador y buscan una forma de detener la cadena de promesas sin informar a todos los consumidores sobre el caso de rechazo, desencadenar cadenas o catch encadenadas o lanzar errores no Uncaught (in promise) , puede usar el seguimiento:

 var noopPromise = { then: () => noopPromise, catch: () => noopPromise } function haltPromiseChain(promise) { promise.catch(noop) return noopPromise } // Use it thus: var p = Promise.reject("some error") p = haltPromiseChain(p) p.catch(e => console.log(e)) // this never happens 

Básicamente, noopPromise es una interfaz básica que cumple con los requisitos y que tiene funciones de encadenamiento, pero nunca ejecuta ninguna. Esto se basa en el hecho de que aparentemente el navegador utiliza pato-tipado para determinar si algo es una promesa, entonces YMMV (lo probé en Chrome 57.0.2987.98), pero si eso se convierte en un problema, probablemente puedas crear una instancia promisoria real y neutralizar sus métodos entonces y captura.