Как перехватить отказы ES6 Promise и полностью остановить поток?

Скажем, у меня есть 4 функции: runA(), runB(), runC() и runD().

Используя обещания ES6, при полностью успешном запуске все они будут выполняться одно за другим:

runA()
.then(runB)
.then(runC)
.then(runD)

Если runA или runB терпят неудачу (отклоняют или бросают), я хотел бы вызвать error1(), а затем полностью остановить цепочку (не вызывать runC или runD). Это заставляет меня думать, что я должен добавить один .catch() в самый конец цепочки обещаний .then:

runA()
.then(runB)
.then(runC)     //won't get called if runA or runB throws
.then(runD)     //won't get called if runA or runB throws
.catch(error1)

Но если runC терпит неудачу, я хотел бы вызвать error2() и все же остановить цепочку (не вызывать runD).

runA()
.then(runB)   
.catch(error1)  //moved up to only handle runA and runB
.then(runC)     //but now this gets called after error1() is run
.then(runD)     
.catch(error2)

Теперь, когда у меня есть 2 вызова catch в цепочке, runC будет вызываться после запуска error1, поскольку результатом перехвата по умолчанию будет resolve. Является ли мой единственный вариант, чтобы функция error1 создавала обещание, которое она всегда отклоняет?


person rwstoneback    schedule 24.12.2015    source источник
comment
Что плохого в использовании только одного .catch()? Вы можете выполнить сортировку ошибок в обратном вызове catch (if (error1) error1() else if (error2) error2()...). Выбрасываемый вами объект Error может иметь сообщение и имя (может быть нужного вам типа, например «RunCError»).   -  person Shanoor    schedule 24.12.2015
comment
конечно, вы имели в виду runA() .then(runB) .then(runC) .then(runD) - ... кроме этого, если вы действительно не можете определить, что вызвало ошибку, вы можете throw в конце error1 пропустить мимо runC и runD - но вы' Мне нужно определить в error2, что ошибка возникла из A или B, а не C или D в любом случае   -  person Jaromanda X    schedule 24.12.2015


Ответы (3)


Нет, создание error1 обещания, которое всегда отклоняется, не ваш единственный вариант.

Вы можете использовать тот факт, что .then принимает два аргумента:

.then(onSuccess, onFailure)

При наличии двух аргументов возникает недооцененный эффект, заключающийся в том, что onFailure не перехватывает сбои в onSuccess. Обычно это нежелательно, за исключением случая, когда вы можете использовать этот факт для разветвления вашего дерева решений:

runA()
.then(runB)
.then(() => runC().then(runD), error1)
.catch(error2)

Это делает то, что вы хотите.

  • если runA или runB терпят неудачу, то вызывается error1 и цепочка останавливается.
  • если runC или runD терпят неудачу, то вызывается error2 и цепочка останавливается.

Вы также можете написать это так:

runA()
.then(runB)
.then(() => runC()
  .then(runD)
  .catch(error2),
error1)

var log = msg => div.innerHTML += "<br>" + msg;

// Change which one of these four rejects, to see behavior:
var runA = () => Promise.resolve().then(() => log("a"));
var runB = () => Promise.reject().then(() => log("b"));
var runC = () => Promise.resolve().then(() => log("c"));
var runD = () => Promise.resolve().then(() => log("d"));
var error1 = () => log("error1");
var error2 = () => log("error2");

runA()
.then(runB)
.then(() => runC().then(runD), error1)
.catch(error2)
<div id="div"></div>

Попробуйте изменить, какой из них не работает в этой скрипте.

person jib    schedule 12.02.2016
comment
Определенно полезная техника, к которой я сначала пытался перейти. Моя проблема с этим заключается в том, что если я хочу поймать исключение или отклонение дальше в цепочке, обратный вызов ошибки из 2-го параметра then приведет к разрешению внешней цепочки... если только этот обратный вызов ошибки не вызвал другое исключение или отклонен, в этом случае это приведет к попаданию следующего оператора catch, что на самом деле не то, что я мог бы хотеть. Я обновил вашу скрипку, указав пример проблемы. - person rwstoneback; 13.02.2016
comment
Ну, это не сработает, если вы не последуете моему ответу, очевидно. Вы должны разветвить цепочку, как я показываю на своей скрипке, а вы этого не делаете. - person jib; 13.02.2016
comment
Взгляните еще раз. Я сделал ветку, но с другим оператором then, следующим за веткой. Таким образом, ваш ответ работает только с веткой, которая является предпоследней частью цепочки. - person rwstoneback; 14.02.2016
comment
Мое решение имеет четыре функции, о которых вы спрашивали. Если вы хотите отклонить внешнюю цепочку, с какой ошибкой ее следует отклонить? Если вы просто хотите остановить цепочку без успеха или неудачи, вы можете сделать .then(new Promise()), но это редко бывает хорошей идеей, хотя я и стараюсь не судить. - person jib; 14.02.2016

Что плохого в использовании только одного .catch()? Вы можете выполнить сортировку ошибок в обратном вызове catch (if (error1) error1() else if (error2) error2()...). Выбрасываемый вами объект Error может иметь сообщение и имя (может быть нужного вам типа, например «RunCError»).

runA()
    .then(runB)
    .then(runC)     // won't get called if runA or runB throws
    .then(runD)     // won't get called if runA or runB throws
    .catch(handleErrors)

function runA() {
    // ...

    if (err) {
        var error = new Error('Something is wrong...');
        error.name = 'RunAError';
        throw error;
    }
}

function runB() {
    // ...

    if (err) {
        var error = new Error('Something is wrong...');
        error.name = 'RunBError';
        throw error;
    }
}

function runC() {
    // ...

    if (err) {
        var error = new Error('Something is wrong...');
        error.name = 'RunCError';
        throw error;
    }
}

function runD() {
    // ...

    if (err) {
        var error = new Error('Something is wrong...');
        error.name = 'RunDError';
        throw error;
    }
}

function handleErrors(err) {
    if (err.name == 'RunAError') {
        handleAError();
    }

    if (err.name == 'RunBError') {
        handleBError();
    }

    // so on...
}
person Shanoor    schedule 24.12.2015
comment
Так редко ли вы захотите использовать более 1 улова в цепочке промисов? И если вы включаете более 1 catch и не хотите, чтобы следующий then запускался после этого улова, функция внутри улова нужно бросить или отклонить снова. - person rwstoneback; 24.12.2015
comment
Да. Это очень похоже на синхронный код. Если вы перехватываете исключение, оно перехватывается, обрабатывается, и положительный поток кода возобновляется. Вот для чего нужен улов. Если это не то, что вы хотите, то либо не ловите его для начала, либо перебрасывайте. В этом случае, если вы вложите уловы, вы ожидаете, что внешние тоже поймают его, верно? - person jib; 14.02.2016

Я только что наткнулся на ту же проблему, и мое решение пока заключается в явном вызове reject в catch, как в этом js-бине: https://jsbin.com/yaqicikaza/edit?js,console

Фрагмент кода

const promise1 = new Promise( ( resolve, reject ) => reject( 42 ) );

promise1
  .catch( ( err ) => console.log( err ) ) // 42 will be thrown here
  .then( ( res ) => console.log( 'will execute' ) ) // then branch will execute


const promise2 = new Promise( ( resolve, reject ) => reject( 42 ) );

promise2
  .catch( ( err ) => Promise.reject( ) ) // trigger rejection down the line
  .then( ( res ) => console.log( 'will not execute' ) ) // this will be skipped

person Alex Moldovan    schedule 08.12.2016