it-swarm.com.de

Brechen Sie die Versprechen-Kette und rufen Sie eine Funktion auf, die auf dem Schritt in der Kette basiert, in dem sie gebrochen ist (verworfen).

Aktualisieren:

Um zukünftigen Zuschauern dieses Beitrags zu helfen, habe ichdiese Demo von Plumas Antwort erstellt.

Frage:

Mein Ziel scheint ziemlich einfach zu sein.

  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); 
  }

Das Problem hier ist, dass, wenn ich in Schritt 1 versage, sowohl stepError(1) als auch stepError(2) ausgelöst werden. Wenn ich nicht return $q.reject, wird stepError(2) nicht ausgelöst, aber step(2) wird, was ich verstehe. Ich habe alles erreicht, außer was ich versuche zu tun.

Wie schreibe ich Versprechen, damit ich bei Ablehnung eine Funktion aufrufen kann, ohne alle Funktionen in der Fehlerkette aufzurufen? Oder gibt es einen anderen Weg, dies zu erreichen?

Hier ist eine Live-Demo damit hast du etwas zu tun.

Aktualisieren:

Ichirgendwiehabe es gelöst. Hier fange ich den Fehler am Ende der Kette ab und übergebe die Daten an reject(data), damit ich weiß, welches Problem in der Fehlerfunktion zu behandeln ist. Dies entspricht eigentlich nicht meinen Anforderungen, da ich nicht auf die Daten angewiesen sein möchte. Es wäre lahm, aber in meinem Fall wäre es sauberer, einen Fehlerrückruf an die Funktion zu übergeben, als von den zurückgegebenen Daten abhängig zu sein, um zu bestimmen, was zu tun ist.

Live-Demo hier (klicken).

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); 
  }
110
m59

Der Grund dafür, dass Ihr Code nicht wie erwartet funktioniert, ist, dass er tatsächlich etwas anderes tut, als Sie glauben.

Nehmen wir an, Sie haben so etwas wie das Folgende:

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

Um besser zu verstehen, was passiert, tun wir so, als wäre dies synchroner Code mit trycatch-Blöcken:

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);
}

Der onRejected-Handler (das zweite Argument von then) ist im Wesentlichen ein Fehlerkorrekturmechanismus (wie ein catch-Block). Wenn ein Fehler in handleErrorOne ausgelöst wird, wird er vom nächsten catch-Block (catch(e2)) usw. abgefangen.

Dies ist offensichtlich nicht das, was Sie beabsichtigt haben.

Nehmen wir an, wir wollen, dass die gesamte Auflösungskette ausfällt, egal was schief geht:

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

Hinweis: Wir können die handleErrorOne dort belassen, wo sie sich befindet, da sie nur aufgerufen wird, wenn stepOne ablehnt (dies ist die erste Funktion in der Kette. Wenn also die Kette an dieser Stelle abgelehnt wird, kann dies nur an dieser Funktion liegen.) versprechen).

Die wichtige Änderung ist, dass die Fehlerbehandlungsroutinen für die anderen Funktionen nicht Teil der Hauptversprechungskette sind. Stattdessen hat jeder Schritt eine eigene "Unterkette" mit einer onRejected, die nur aufgerufen wird, wenn der Schritt abgelehnt wurde (aber von der Hauptkette nicht direkt erreicht werden kann).

Der Grund dafür ist, dass sowohl onFulfilled als auch onRejected optionale Argumente für die then-Methode sind. Wenn ein Versprechen erfüllt wird (d. H. Gelöst wird) und die nächste then in der Kette keinen onFulfilled-Handler hat, wird die Kette fortgesetzt, bis es einen solchen Handler gibt.

Dies bedeutet, dass die folgenden zwei Zeilen gleichwertig sind:

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

Die folgende Zeile ist jedoch nicht äquivalent zu den beiden obigen:

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

Angulars Versprechenbibliothek $q basiert auf der Q-Bibliothek von kriskowal (die über eine umfangreichere API verfügt, jedoch alles enthält, was Sie in $q finden können). Qs API-Dokumente auf GitHub könnten sich als nützlich erweisen. Q implementiert das Promises/A + spec , das detailliert aufzeigt, wie then und das Versprechungsauflösungsverhalten genau funktionieren.

BEARBEITEN:

Denken Sie auch daran, dass Sie, wenn Sie in Ihrem Error-Handler aus der Kette aussteigen möchten, ein abgelehntes Versprechen zurückgeben oder einen Fehler werfen müssen (der automatisch abgefangen und in ein abgelehntes Versprechen eingepackt wird). Wenn Sie kein Versprechen zurückgeben, fasst then den Rückgabewert in ein Auflösungsversprechen für Sie ein. 

_/Dies bedeutet, dass Sie, wenn Sie nichts zurückgeben, tatsächlich ein gelöstes Versprechen für den Wert undefined zurückgeben.

165
Alan Plum

Etwas spät zur Party, aber diese einfache Lösung hat für mich funktioniert:

function chainError(err) {
  return Promise.reject(err)
};

stepOne()
.then(stepTwo, chainError)
.then(stepThreee, chainError);

Dadurch können Sie break aus der Kette ausbrechen.

44
Vinnyq12

Was Sie brauchen, ist eine sich wiederholende .then()-Kette mit einem Sonderfall zum Starten und einem Sonderfall zum Abschließen.

Der Kniff besteht darin, die Schrittnummer des Fehlerfalls dazu zu bringen, sich an einen endgültigen Fehlerbehandler zu wenden.

  • Start: Rufen Sie step(1) unbedingt auf.
  • Wiederholungsmuster: Verketten Sie eine .then() mit den folgenden Rückrufen:
    • erfolg: Aufrufschritt (n + 1)
    • fehler: Der Wert wird verworfen, mit dem das zuvor verletzte Produkt abgelehnt wurde, oder der Fehler wird erneut ausgelöst.
  • Finish: Verketten Sie eine .then() ohne Erfolgsbehandlung und einen abschließenden Fehlerbehandler.

Sie können das Ganze mit der langen Hand schreiben, aber es ist einfacher, das Muster mit benannten, verallgemeinerten Funktionen zu demonstrieren:

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);});

siehe Demo

Beachten Sie, wie in step() das Zurückgestellte mit n abgelehnt oder aufgelöst wird, wodurch dieser Wert für die Rückrufe in der nächsten .then() in der Kette verfügbar ist. Sobald stepError aufgerufen wird, wird der Fehler wiederholt wiederholt, bis er von finalError verarbeitet wird.

9

Beim Ablehnen sollten Sie einen Ablehnungsfehler übergeben und dann Schritt-Fehler-Handler in eine Funktion einschließen, die prüft, ob die Ablehnung bis zum Ende der Kette verarbeitet oder "neu gestartet" werden soll

// 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);
  });

Was Sie auf der Konsole sehen würden:

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

Hier ist ein funktionierender Code https://jsfiddle.net/8hzg5s7m/3/

Wenn Sie für jeden Schritt eine bestimmte Handhabung haben, könnte Ihr Wrapper ungefähr so ​​aussehen:

/*
 * 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);    
    }
}

dann deine Kette

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);
});
6
redben

Wenn ich es richtig verstanden habe, möchten Sie nur den Fehler für den fehlgeschlagenen Schritt anzeigen, oder?

Das sollte so einfach sein wie das Scheitern des ersten Versprechens:

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

Wenn Sie $q.reject() im Fehlerfall des ersten Schritts zurückgeben, lehnen Sie dieses Versprechen ab, wodurch der errorCallback in der 2. then(...) aufgerufen wird.

2
Zajn
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

Oder automatisiert für beliebig viele Schritte:

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

2
Esailija

Hängen Sie Fehlerbehandlungsroutinen als separate Kettenelemente direkt an die Ausführung der Schritte an:

        // 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(); });
});

oder mit 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(); });
});

Hinweis: Dies ist im Grunde das gleiche Muster, das pluma in seiner Antwort vorschlägt aber unter Verwendung der OP-Benennung.

1
Ignitor

Gefundene Promise.prototype.catch() Beispiele auf MDN unten sind sehr hilfreich.

(Die akzeptierte Antwort erwähnt then(null, onErrorHandler), was im Grunde dasselbe ist wie catch(onErrorHandler).)

Verwendung und Verkettung der Fangmethode

var p1 = new Promise(function(resolve, reject) {
  resolve('Success');
});

p1.then(function(value) {
  console.log(value); // "Success!"
  throw 'oh, no!';
}).catch(function(e) {
  console.log(e); // "oh, no!"
}).then(function(){
  console.log('after a catch the chain is restored');
}, function () {
  console.log('Not fired due to the catch');
});

// The following behaves the same as above
p1.then(function(value) {
  console.log(value); // "Success!"
  return Promise.reject('oh, no!');
}).catch(function(e) {
  console.log(e); // "oh, no!"
}).then(function(){
  console.log('after a catch the chain is restored');
}, function () {
  console.log('Not fired due to the catch');
});

Gotchas beim Werfen von Fehlern

// Throwing an error will call the catch method most of the time
var p1 = new Promise(function(resolve, reject) {
  throw 'Uh-oh!';
});

p1.catch(function(e) {
  console.log(e); // "Uh-oh!"
});

// Errors thrown inside asynchronous functions will act like uncaught errors
var p2 = new Promise(function(resolve, reject) {
  setTimeout(function() {
    throw 'Uncaught Exception!';
  }, 1000);
});

p2.catch(function(e) {
  console.log(e); // This is never called
});

// Errors thrown after resolve is called will be silenced
var p3 = new Promise(function(resolve, reject) {
  resolve();
  throw 'Silenced Exception!';
});

p3.catch(function(e) {
   console.log(e); // This is never called
});

Wenn es gelöst ist

//Create a promise which would not call onReject
var p1 = Promise.resolve("calling next");

var p2 = p1.catch(function (reason) {
    //This is never called
    console.log("catch p1!");
    console.log(reason);
});

p2.then(function (value) {
    console.log("next promise's onFulfilled"); /* next promise's onFulfilled */
    console.log(value); /* calling next */
}, function (reason) {
    console.log("next promise's onRejected");
    console.log(reason);
});
1
toraritte

Wenn Sie dieses Problem mit async/await lösen möchten:

(async function(){    
    try {        
        const response1, response2, response3
        response1 = await promise1()

        if(response1){
            response2 = await promise2()
        }
        if(response2){
            response3 = await promise3()
        }
        return [response1, response2, response3]
    } catch (error) {
        return []
    }

})()
1
luispa

Die beste Lösung besteht darin, Ihre Versprechen-Kette zu refactorieren, um ES6 awaits zu nutzen. Dann können Sie einfach von der Funktion zurückkehren, um den Rest des Verhaltens zu überspringen.

Ich habe seit über einem Jahr meinen Kopf gegen dieses Muster geschlagen, und "waitit" ist der Himmel.

0
Pete Alvin

Versuchen Sie, dies wie libs zu verwenden:

https://www.npmjs.com/package/promise-chain-break

    db.getData()
.then(pb((data) => {
    if (!data.someCheck()) {
        tellSomeone();

        // All other '.then' calls will be skiped
        return pb.BREAK;
    }
}))
.then(pb(() => {
}))
.then(pb(() => {
}))
.catch((error) => {
    console.error(error);
});
0
Leonid