Должен ли я воздерживаться от асинхронного обращения с обещанием?

Я только что установил Node v7.2.0 и узнал, что следующий код:

var prm = Promise.reject(new Error('fail'));

приводит к этому сообщению:;

(node:4786) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: fail
(node:4786) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Я понимаю причины этого, так как многие программисты, вероятно, испытали разочарование Error в результате проглатывания Promise. Однако затем я сделал этот эксперимент:

var prm = Promise.reject(new Error('fail'));

setTimeout(() => {
    prm.catch((err) => {
        console.log(err.message);
    })
},
0)

что приводит к:

(node:4860) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: fail
(node:4860) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
(node:4860) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
fail

I на основе PromiseRejectionHandledWarning предполагает, что обращение с a Promise асинхронно является/может быть плохой.

Но почему это?

Ответ 1

"Должен ли я воздерживаться от асинхронного обращения с обещанием?"

Эти предупреждения служат важной цели, но чтобы увидеть, как все это работает, см. эти примеры:

Попробуйте следующее:

process.on('unhandledRejection', () => {});
process.on('rejectionHandled', () => {});

var prm = Promise.reject(new Error('fail'));

setTimeout(() => {
    prm.catch((err) => {
        console.log(err.message);
    })
}, 0);

Или это:

var prm = Promise.reject(new Error('fail'));
prm.catch(() => {});

setTimeout(() => {
    prm.catch((err) => {
        console.log(err.message);
    })
}, 0);

Или это:

var var caught = require('caught');
var prm = caught(Promise.reject(new Error('fail')));

setTimeout(() => {
    prm.catch((err) => {
        console.log(err.message);
    })
}, 0);

Отказ от ответственности: я являюсь автором модуля caught (и да, я написал его для этого ответа).

Обоснование

Он был добавлен в Node как один из Нарушение изменений между v6 и v7. Об этом шла горячая дискуссия в Проблема № 830: Поведение по умолчанию для необработанного отклонения по умолчанию без универсального соглашения о том, как promises с обработчиками отклонения подключены асинхронно должен вести себя - работать без предупреждений, работать с предупреждениями или вообще запрещаться к использованию, завершая программу. Больше обсуждений было проведено в нескольких выпусках проекта unhandled-rejections-spec.

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

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

Предотвращение предупреждений

Прикрепление пустого обработчика не меняет способ сохранения хранимого обещания, если вы делаете это в два этапа:

var prm1 = Promise.reject(new Error('fail'));
prm1.catch(() => {});

Это не одно и то же:

var prm2 = Promise.reject(new Error('fail')).catch(() => {});

Здесь prm2 будет другим обещанием тогда prm1. Пока prm1 будет отклонен с ошибкой "fail", prm2 будет разрешен с помощью undefined, который, вероятно, не является тем, что вы хотите.

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

var prm3 = caught(Promise.reject(new Error('fail')));

Здесь prm3 совпадает с prm1.

Смотрите: https://www.npmjs.com/package/caught

2017 Обновление

См. также Pull Request # 6375: lib, src: "бросить" на необработанные обетованные отказы (еще не объединился с февраля 2017 года), что отмечен как Milestone 8.0.0:

Делает отклонения promises "throw", которые выходят, как обычные неперехваченные ошибки. [выделено мной]

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

См. также Node.js 8.0.0 Отслеживание проблемы № 10117.

Ответ 2

Я полагаю, что обращение с отказом Promise асинхронно - это плохо.

Да, действительно.

Ожидается, что вы все равно будете обращаться с любыми отклонениями. Если вы не сделаете (и, возможно, никогда не справитесь с этим), вы получите предупреждение.
Я почти не испытывал ситуаций, когда вы не хотели бы терпеть неудачу сразу после получения отказа. И даже если вам нужно ждать чего-то еще после сбоя, вы должны сделать это явно.

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

var prm = Promise.reject(new Error('fail'));

prm.catch((err) => {
    setTimeout(() => {
        console.log(err.message);
    }, 0);
});