Покажите мне ваши промисы


Вчера один опытный инженер показывал цепочку промисов:

// promise.then(onFulfilled: Function[, onRejected: Function]);

Promise.resolve("")
    .then(a, b)
    .then(c, d)
    .then(e, f);

И спрашивал что будет, если a падает (то бишь бросает):

Promise.resolve("")
    .then(_ => { throw new Error("A") }, b)
    .then(c, d)
    .then(e, f);

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

И спросил что будет дальше. А я и растерялся, потому что в промисах не алё и сам не понимаю как так вышло. Вроде и много раз видел, и пользовался даже, но сказал, что нормальная цепочка должена иметь .catch в хвосте, куда и должны прилетать все исключения.

Promise.resolve("")
    .then(_ => {throw new Error("💩")}, b)
    .then(c, d)
    .then(e, f)
    .catch(val => {/* Error 💩 */});

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

Ну ладно, добавим в копилку еще один js wtf, где исключение это то же самое, что и rejected promise, но дальше то что? Ну я и говорю, что всё. Дальше некуда, навыполнялись. А промис взял и начал выполнять e, принимая значение из d. Еще раз, выполнять e. После d. Мы вызвали onFulfilled с результатом из onRejected. Что? Зачем?

Надо тебе в потоке данные модифицировать, ну возьми ты стрим. Надо в цепочке что-то посчитать — так есть же Either. Не надо жонглировать факелами, стоя на бритве.