Подтвердить что ты не робот

Как вернуться из Promise catch/затем заблокировать?

Существует много руководств о том, как использовать "then" и "catch" при программировании с помощью JavaScript Promise. Тем не менее, все эти учебные пособия, похоже, пропустят важный момент: возвращение из блока then/catch, чтобы разбить цепочку Promise. Начнем с некоторого синхронного кода, чтобы проиллюстрировать эту проблему:

try {
  someFunction();
} catch (err) {
  if (!(err instanceof MyCustomError))
    return -1;
}
someOtherFunction();

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

Promise.resolve(someFunction).then(function() {
  console.log('someFunction should throw error');
  return -2;
}).catch(function(err) {
   if (err instanceof MyCustomError) {
     return -1;
   }
}).then(someOtherFunction);

Эта логика используется для некоторых моих модульных тестов, где я хочу, чтобы функция терпит неудачу определенным образом. Даже если я изменю catch на тот блок, я все еще не могу сломать цепочку цепочек Promises, потому что все, что возвращается из блока then/catch, станет Promise, распространяющимся по цепочке.

Интересно, может ли Promise достичь этой логики; если нет, то почему? Мне очень странно, что цепь обещаний никогда не может быть нарушена. Спасибо!

Изменить на 08/16/2015: Согласно ответам, данным до сих пор, отклоненное обещание, возвращенное тогдашним блоком, будет распространяться через цепочку обещаний и пропускать все последующие блоки до тех пор, пока оно не будет поймано (обработано). Это поведение хорошо понято, потому что оно просто имитирует следующий синхронный код (подход 1):

try {
  Function1();
  Function2();
  Function3();
  Function4();
} catch (err) {
  // Assuming this err is thrown in Function1; Function2, Function3 and Function4 will not be executed
  console.log(err);
}

Однако то, что я спрашивал, это следующий сценарий синхронного кода (подход 2):

try {
  Function1();
} catch(err) {
  console.log(err); // Function1 error
  return -1; // return immediately
}
try {
  Function2();
} catch(err) {
  console.log(err);
}
try {
  Function3();
} catch(err) {
  console.log(err);
}
try {
  Function4();
} catch(err) {
  console.log(err);
} 

Я хотел бы иметь дело с ошибками, возникающими в разных функциях по-разному. Возможно, я поймаю все ошибки в одном блоке catch, как показано в подходе 1. Но таким образом я должен сделать большой оператор switch внутри блока catch, чтобы различать различные ошибки; кроме того, если ошибки, вызванные разными функциями, не имеют общего переключаемого атрибута, я вообще не смогу использовать оператор switch; в такой ситуации я должен использовать отдельный блок try/catch для каждого вызова функции. Подход 2 иногда является единственным вариантом. Разве Promise не поддерживает этот подход с помощью инструкции then/catch?

4b9b3361

Ответ 1

Этого нельзя достичь с помощью особенностей языка. Тем не менее, доступны решения на основе шаблонов.

Вот два решения.

Повторить предыдущую ошибку

Этот шаблон в основном звучит...

Promise.resolve()
.then(Function1).catch(errorHandler1)
.then(Function2).catch(errorHandler2)
.then(Function3).catch(errorHandler3)
.then(Function4).catch(errorHandler4)
.catch(finalErrorHandler);

Promise.resolve() не является строго необходимым, но позволяет всем линиям .then().catch() иметь один и тот же шаблон, и все выражение легче на глазу.

... но:

  • если errorHandler возвращает результат, цепочка будет переходить к следующему обработчику успеха линии.
  • если ошибкаHandler выдает, то цепочка будет переходить к следующему обработчику ошибок строки.

Желаемый прыжок из цепочки не произойдет, если обработчики ошибок не будут записаны так, чтобы они могли различать ранее заброшенную ошибку и свежую ошибку. Например:

function errorHandler1(error) {
    if (error instanceof MyCustomError) { // <<<<<<< test for previously thrown error 
        throw error;
    } else {
        // do errorHandler1 stuff then
        // return a result or 
        // throw new MyCustomError() or 
        // throw new Error(), new RangeError() etc. or some other type of custom error.
    }
}

Теперь:

  • если errorHandler возвращает результат, цепочка переходит к следующему FunctionN.
  • если ошибкаHandler выдает MyCustomError, то она будет повторно возвращена в цепочку и поймана первым обработчиком ошибок, который не соответствует протоколу if(error instanceof MyCustomError) (например, final.catch()).
  • если ошибкаHandler вызывает какой-либо другой тип ошибки, цепочка будет переходить к следующему catch.

Этот шаблон был бы полезен, если вам нужна гибкость, чтобы перейти к концу цепочки или нет, в зависимости от типа ошибки. Ожидаются редкие обстоятельства.

DEMO

Изолированные уловы

Другим решением является введение механизма сохранения каждого .catch(errorHandlerN) "изолированного" таким образом, чтобы он ломал только ошибки, возникающие из его соответствующих FunctionN, а не из предыдущих ошибок.

Это может быть достигнуто за счет наличия в основной цепочке только обработчиков успеха, каждая из которых содержит анонимную функцию, содержащую подцепи.

Promise.resolve()
.then(function() { return Function1().catch(errorHandler1); })
.then(function() { return Function2().catch(errorHandler2); })
.then(function() { return Function3().catch(errorHandler3); })
.then(function() { return Function4().catch(errorHandler4); })
.catch(finalErrorHandler);

Здесь Promise.resolve() играет важную роль. Без него Function1().catch(errorHandler1) будет в основной цепочке catch() не будет изолирован от основной цепочки.

Теперь

  • если errorHandler возвращает результат, цепочка перейдет к следующей строке.
  • если errorHandler выбрасывает все, что ему нравится, тогда цепочка будет двигаться непосредственно к finalErrorHandler.

Используйте этот шаблон, если вы всегда хотите пропустить до конца цепи, независимо от типа ошибки. Пользовательский конструктор ошибок не требуется, и обработчики ошибок не нужно писать особым образом.

DEMO

Примеры использования

Какой шаблон выбрать, будет определяться уже рассмотренными соображениями, но, возможно, и по характеру вашей проектной команды.

  • Команда из одного человека - вы пишете все и понимаете проблемы - если вы свободны в выборе, а затем выполняйте свои личные предпочтения.
  • Многопользовательская команда - один человек пишет главную цепочку, а другие другие пишут функции и их обработчики ошибок - если можно, выберите "Изолированные уловы" - все, что находится под контролем главной цепочки, вам не нужно принудительно применять дисциплина написания обработчиков ошибок определенным образом.

Ответ 2

Во-первых, я вижу распространенную ошибку в этом разделе кода, которая может полностью сбить вас с толку. Это ваш пример кода:

Promise.resolve(someFunction()).then(function() {
  console.log('someFunction should throw error');
  return -2;
}).catch(function(err) {
   if (err instanceof MyCustomError) {
     return -1;
   }
}).then(someOtherFunction());

Вам нужно передать ссылки на функции в обработчик .then(), а не вызывать функцию и передавать их возвращаемый результат. Итак, приведенный выше код, вероятно, должен быть таким:

Promise.resolve(someFunction()).then(function() {
  console.log('someFunction should throw error');
  return -2;
}).catch(function(err) {
   if (err instanceof MyCustomError) {
     // returning a normal value here will take care of the rejection
     // and continue subsequent processing
     return -1;
   }
}).then(someOtherFunction);    // just pass function reference here

Обратите внимание, что я удалил () после функций в обработчике .then(), так что вы просто передаете ссылку на функцию, а не сразу вызываете функцию. Это позволит инфраструктуре обещания решать, следует ли в будущем вызвать обещание или нет. Если вы допустили эту ошибку, это полностью отбросит вас за то, как работают обещания, потому что все будет вызвано независимо.


Три простых правила отлова отказов.

  1. Если никто не уловит отказ, он немедленно останавливает цепочку обещаний, и первоначальный отказ становится окончательным состоянием обещания. Последующие обработчики не вызываются.
  2. Если отклонение обещания обнаруживается и либо ничего не возвращается, либо возвращается какое-либо нормальное значение из обработчика отклонения, то отклонение считается обработанным, цепочка обещаний продолжается и вызываются последующие обработчики. Все, что вы возвращаете из обработчика отклонения, становится текущим значением обещания, и это так, как если бы отклонение никогда не происходило (за исключением того, что этот уровень обработчика разрешения не вызывался - вместо этого вызывался обработчик отклонения).
  3. Если отклонение обещания обнаружено, и вы либо выдаваете ошибку из обработчика отклонения, либо возвращаете отклоненное обещание, то все обработчики разрешения пропускаются до следующего обработчика отклонения в цепочке. Если обработчики отклонения отсутствуют, цепочка обещаний останавливается, и вновь созданная ошибка становится окончательным состоянием обещания.

Вы можете увидеть пару примеров в этом jsFiddle, где показаны три ситуации:

  1. Возвращая обычное значение из обработчика отклонения, вызывает следующий обработчик разрешения .then() (например, нормальная обработка продолжается),

  2. Добавление обработчика отклонения приводит к остановке обычной обработки разрешения, и все обработчики разрешения пропускаются до тех пор, пока вы не доберетесь до обработчика отклонения или конца цепочки. Это эффективный способ остановить цепочку, если в обработчике разрешения обнаружена непредвиденная ошибка (я думаю, это ваш вопрос).

  3. Отсутствие обработчика отклонения приводит к остановке обычной обработки разрешения, и все обработчики разрешения пропускаются до тех пор, пока вы не доберетесь до обработчика отклонения или конца цепочки.

Ответ 3

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

doSomething()
  .then(func1).catch(handleError)
  .then(func2).catch(handleError)
  .then(func3).catch(handleError);

function handleError(reason) {
  if (reason instanceof criticalError) {
    throw reason;
  }

  console.info(reason);
}

Если какой-либо из блоков catch поймал a criticalError, они пропустили бы прямо до конца и выбросили ошибку. Любая другая ошибка будет регистрироваться в консоли и до перехода к следующему блоку .then.