promises promesas example array all javascript angularjs promise

javascript - example - promesas angular 4



Rompe cadena de promesas y llama a una función basada en el paso de la cadena donde está rota(rechazada) (7)

Adjunte controladores de errores como elementos de cadena separados directamente a la ejecución de los pasos:

// Handle errors for step(1) step(1).then(null, function() { stepError(1); return $q.reject(); }) .then(function() { // Attach error handler for step(2), // but only if step(2) is actually executed return step(2).then(null, function() { stepError(2); return $q.reject(); }); }) .then(function() { // Attach error handler for step(3), // but only if step(3) is actually executed return step(3).then(null, function() { stepError(3); return $q.reject(); }); });

o usando catch() :

// Handle errors for step(1) step(1).catch(function() { stepError(1); return $q.reject(); }) .then(function() { // Attach error handler for step(2), // but only if step(2) is actually executed return step(2).catch(function() { stepError(2); return $q.reject(); }); }) .then(function() { // Attach error handler for step(3), // but only if step(3) is actually executed return step(3).catch(function() { stepError(3); return $q.reject(); }); });

Nota: Este es básicamente el mismo patrón que sugiere la pluma en su respuesta, pero usando la denominación del OP.

Actualizar:

Para ayudar a los futuros espectadores de esta publicación, creé esta demostración de la respuesta de pluma .

Pregunta:

Mi objetivo parece bastante sencillo.

step(1) .then(function() { return step(2); }, function() { stepError(1); return $q.reject(); }) .then(function() { }, function() { stepError(2); }); function step(n) { var deferred = $q.defer(); //fail on step 1 (n === 1) ? deferred.reject() : deferred.resolve(); return deferred.promise; } function stepError(n) { console.log(n); }

El problema aquí es que si fallo en el paso 1, se stepError(1) tanto stepError(1) stepError(2) . Si no return $q.reject entonces stepError(2) no se disparará, pero el step(2) hará, lo cual entiendo. Logré todo excepto lo que estoy tratando de hacer.

¿Cómo escribo las promesas para que pueda llamar a una función sobre rechazo, sin llamar a todas las funciones en la cadena de error? ¿O hay otra forma de lograr esto?

Aquí hay una demostración en vivo para que pueda trabajar con algo.

Actualizar:

Yo como que lo he resuelto. Aquí capturo el error al final de la cadena y paso los datos a reject(data) para saber qué problema manejar en la función de error. En realidad, esto no cumple mis requisitos porque no quiero depender de los datos. Sería poco convincente, pero en mi caso sería más limpio pasar una devolución de llamada de error a la función en lugar de depender de los datos devueltos para determinar qué hacer.

Demostración en vivo aquí (click).

step(1) .then(function() { return step(2); }) .then(function() { return step(3); }) .then(false, function(x) { stepError(x); } ); function step(n) { console.log(''Step ''+n); var deferred = $q.defer(); (n === 1) ? deferred.reject(n) : deferred.resolve(n); return deferred.promise; } function stepError(n) { console.log(''Error ''+n); }


Cuando rechace, debe pasar un error de rechazo, luego ajuste los manejadores de error de paso en una función que verifique si el rechazo debe procesarse o rehacerse hasta el final de la cadena:

// function mocking steps function step(i) { i++; console.log(''step'', i); return q.resolve(i); } // function mocking a failing step function failingStep(i) { i++; console.log(''step ''+ i + '' (will fail)''); var e = new Error(''Failed on step '' + i); e.step = i; return q.reject(e); } // error handler function handleError(e){ if (error.breakChain) { // handleError has already been called on this error // (see code bellow) log(''errorHandler: skip handling''); return q.reject(error); } // firs time this error is past to the handler console.error(''errorHandler: caught error '' + error.message); // process the error // ... // error.breakChain = true; return q.reject(error); } // run the steps, will fail on step 4 // and not run step 5 and 6 // note that handleError of step 5 will be called // but since we use that error.breakChain boolean // no processing will happen and the error will // continue through the rejection path until done(,) step(0) // 1 .catch(handleError) .then(step) // 2 .catch(handleError) .then(step) // 3 .catch(handleError) .then(failingStep) // 4 fail .catch(handleError) .then(step) // 5 .catch(handleError) .then(step) // 6 .catch(handleError) .done(function(){ log(''success arguments'', arguments); }, function (error) { log(''Done, chain broke at step '' + error.step); });

Lo que verías en la consola:

step 1 step 2 step 3 step 4 (will fail) errorHandler: caught error ''Failed on step 4'' errorHandler: skip handling errorHandler: skip handling Done, chain broke at step 4

Aquí hay un código de trabajo https://jsfiddle.net/8hzg5s7m/3/

Si tiene un manejo específico para cada paso, su contenedor podría ser algo como:

/* * simple wrapper to check if rejection * has already been handled * @param function real error handler */ function createHandler(realHandler) { return function(error) { if (error.breakChain) { return q.reject(error); } realHandler(error); error.breakChain = true; return q.reject(error); } }

entonces tu cadena

step1() .catch(createHandler(handleError1Fn)) .then(step2) .catch(createHandler(handleError2Fn)) .then(step3) .catch(createHandler(handleError3Fn)) .done(function(){ log(''success''); }, function (error) { log(''Done, chain broke at step '' + error.step); });


La razón por la que su código no funciona como se espera es que realmente está haciendo algo diferente de lo que cree que hace.

Digamos que tiene algo como lo siguiente:

stepOne() .then(stepTwo, handleErrorOne) .then(stepThree, handleErrorTwo) .then(null, handleErrorThree);

Para comprender mejor lo que está sucediendo, imaginemos que se trata de un código sincrónico con bloques try / catch :

try { try { try { var a = stepOne(); } catch(e1) { a = handleErrorOne(e1); } var b = stepTwo(a); } catch(e2) { b = handleErrorTwo(e2); } var c = stepThree(b); } catch(e3) { c = handleErrorThree(e3); }

El controlador onRejected (el segundo argumento de then ) es esencialmente un mecanismo de corrección de errores (como un bloque catch ). Si se arroja un error en handleErrorOne , será capturado por el siguiente bloque catch(e2) ), y así sucesivamente.

Esto obviamente no es lo que pretendías.

Digamos que queremos que toda la cadena de resolución falle sin importar lo que vaya mal:

stepOne() .then(function(a) { return stepTwo(a).then(null, handleErrorTwo); }, handleErrorOne) .then(function(b) { return stepThree(b).then(null, handleErrorThree); });

Nota: Podemos dejar handleErrorOne donde está, porque solo se invocará si stepOne rechaza (es la primera función en la cadena, por lo que sabemos que si la cadena es rechazada en este punto, solo puede ser debido a esa función promesa).

El cambio importante es que los manejadores de errores para las otras funciones no son parte de la cadena principal de promesas. En cambio, cada paso tiene su propia " onRejected " con un onRejected que solo se onRejected si el paso fue rechazado (pero no puede ser alcanzado directamente por la cadena principal).

El motivo por el que esto funciona es que tanto en onFulfilled como en onRejected son argumentos opcionales para el método de then . Si se cumple una promesa (es decir, se resuelve) y la siguiente en la cadena no tiene un controlador en onFulfilled , la cadena continuará hasta que haya una con dicho controlador.

Esto significa que las siguientes dos líneas son equivalentes:

stepOne().then(stepTwo, handleErrorOne) stepOne().then(null, handleErrorOne).then(stepTwo)

Pero la siguiente línea no es equivalente a las dos anteriores:

stepOne().then(stepTwo).then(null, handleErrorOne)

La biblioteca de promesas de Angular $q se basa en la biblioteca Q de kriskowal (que tiene una API más rica, pero contiene todo lo que puede encontrar en $q ). Los documentos API de Q en GitHub podrían ser útiles. Q implementa la especificación Promises / A + , que detalla cómo funcionan exactamente así el comportamiento de resolución de promesa.

EDITAR:

También tenga en cuenta que si desea salir de la cadena en su controlador de errores, debe devolver una promesa rechazada o lanzar un Error (que será capturado y envuelto en una promesa rechazada automáticamente). Si no devuelve una promesa, envuelve el valor devuelto en una promesa de resolución para usted.

Esto significa que si no devuelve nada, está efectivamente devolviendo una promesa resuelta para el valor undefined .


Lo que necesita es una cadena .then() repetitiva con un estuche especial para comenzar y un estuche especial para terminar.

El truco consiste en hacer que el número de paso del caso de falla se propague a través de un manejador de error final.

  • Inicio: llame al step(1) incondicionalmente.
  • Patrón .then() : encadena a .then() con las siguientes devoluciones de llamada:
    • éxito: paso de llamada (n + 1)
    • falla: arrojar el valor con el que se rechazó el deferered previo o volver a lanzar el error.
  • Finalizar: encadena a .then() sin controlador de éxito y controlador de error final.

Puede escribir todo a mano, pero es más fácil demostrar el patrón con funciones generalizadas y nombradas:

function nextStep(n) { return step(n + 1); } function step(n) { console.log(''step '' + n); var deferred = $q.defer(); (n === 3) ? deferred.reject(n) : deferred.resolve(n); return deferred.promise; } function stepError(n) { throw(n); } function finalError(n) { console.log(''finalError '' + n); } step(1) .then(nextStep, stepError) .then(nextStep, stepError) .then(nextStep, stepError) .then(nextStep, stepError) .then(nextStep, stepError) .then(null, finalError);});

ver demo

Observe cómo en el step() , el diferido se rechaza o se resuelve con n , lo que hace que ese valor esté disponible para las devoluciones de llamada en el siguiente .then() de la cadena. Una vez que se stepError , el error se vuelve a lanzar repetidamente hasta que sea manejado por finalError .


Si entiendo correctamente, solo quiere que se muestre el error para mostrar el paso fallido, ¿verdad?

Eso debería ser tan simple como cambiar el caso de falla de la primera promesa a esto:

step(1).then(function (response) { step(2); }, function (response) { stepError(1); return response; }).then( ... )

Al devolver $q.reject() en el caso de falla del primer paso, está rechazando esa promesa, lo que hace que se llame al errorCallback en el segundo y then(...) .


Un poco tarde para la fiesta, pero esta solución simple funcionó para mí:

function chainError(err) { return Promise.reject(err) }; stepOne() .then(stepTwo, chainError) .then(stepThreee, chainError);

Esto le permite salir de la cadena.


var s = 1; start() .then(function(){ return step(s++); }) .then(function() { return step(s++); }) .then(function() { return step(s++); }) .then(0, function(e){ console.log(s-1); });

http://jsbin.com/EpaZIsIp/20/edit

O automatizado para cualquier cantidad de pasos:

var promise = start(); var s = 1; var l = 3; while(l--) { promise = promise.then(function() { return step(s++); }); } promise.then(0, function(e){ console.log(s-1); });

http://jsbin.com/EpaZIsIp/21/edit