Comments 20
Можно, но не совсем прямо. Например конструкция
async function myFunc() { ... }
вернет системный промис, если немного не постараться. А на системных промисах некоторых bluebird-специфичных вещей нет. И когда не знаешь про это (а дебагер и то и другое показывает как Promise
), то самый первый дебаг получается увлекательный.
Писать полу-асинхронные функции — плохая практика. Вместо того, чтобы вынуждать всех потребителей писать Promise.try()
, лучше заставить ее всегда возвращать промис
function semiAsyncFunction() {
return new Promise((resolve, reject) => {
// синхронные исключения здесь тоже зарежектят промис
});
}
Поэтому я бы в качестве замены для Promise.try(...) рассматривал комбинацию Promise.resolve().then(...)
Что-то не то с названием. Мне кажется, что когда пишут "магия внутри", то в статье будет про то, как это внутри работает (а там довольно интересные дела внутри с точки зрения оптимизации происходят), а не вольный пересказ api reference.
Для некоторых из указанных штук, если неплохие альтернативы в нативном async/await
1) Promise.protype.finally()
. Здесь все очевидно
try {
doSomething()
}
catch(e) {}
finally {
cleanup()
}
2) Promise.any
. В стандарте есть похожий Promise.race. У него отличается поведение в случае ошибки, нет AggregationError, но для типичной задачи "берем данные либо по сети, либо из кеша" — работает неплохо:
const result = await Promise.race([tryNetwork(), tryCache(), timeout()])
3) Promise.mapSeries
заменяется на обычный for-of цикл
for (const i of [10, 20, 30]) {
await doRequest(i);
}
4) В дополнение к своему комменту выше про Promise.try замечу, что с async-функциями, даже это не нужно. Асинхронные функции не выкидывают синхронных исключений по определению (что логично, в общем-то).
5) Promise.bind()
в async/await коде не нужен, можно просто обойтись переменными:
const user = await fetchUser();
const orders = await fetchOrders(user);
const processed = await checkProcessedOrders(user, orders);
В цепочках then
значения user и orders приходилось бы как-то передавать, а здесь все выглядит естественно.
Аналогично с Promise.tap
методом, теперь нет проблем вставить вызов посередине
const user = await fetchUser();
await delay(300);
const orders = await fetchOrders(user);
6) Отмену промисов уже завезли в fetch API. Будет доступна с следующем релизе Хрома (66), пока можно поиграться в канарейке.
P.S. ни в коем случае не принижаю полезность bluebird, но если вы решили использовать его только по одной из 6 причин показанных выше, то сперва стоит посмотреть на нативные возможности
P.P.S фича с опциональным перехватом исключений по типу или другому предикату — огонь! Такого даже в Typescript нет. Но реальные use-case для развесистой обработки ошибок по типу встречаются нечасто, обычно достаточно пары if-ов. Но подход красивый, не спорю.
2) Все таки `Promise.race` отличается и поэтому имеет другое применение. Он ведь перейдет в состояние rejected как только любой из переданных промисов перейдет в rejected. Поэтому наиболее частое применение `Promise.race` это конкурирование запроса с каким-либо событием, например, с таймаутом. С другой стороны, если у нас есть несколько источников информации и мы хотим дождаться ответа от любого из них и не получать ошибку до тех пор пока все источники не вернут ошибку, то будем использовать `Promise.any`. Надеюсь не запутал :)
6) С bluebird мы можем любой промис сделать отменяемым + если операция позволяет, отменить и ее
P.S. Использую bluebird в основном из-за map, any и скорости
С bluebird мы можем любой промис сделать отменяемым + если операция позволяет, отменить и ее
С отменяемостью в bluebird неоднозначно.
Во-первых promise.cancel()
не отменит операцию, если у нее будет второй консьюмер
var result = fetch(...);
var first = result.then(...);
var second = result.then(...);
first.cancel(); // ничего не произойдет
Вторая проблема, что при отмене промиса, он так и зависнет в неопределенном состоянии. finally вызовутся, а then/catch нет.
Вариант с AbortController мне больше нравится. Есть явный метод, который нужно дернуть, чтобы промис отменился, а его потребители получат конкретную ошибку, а не зависнут навечно.
Давайте разбираться вместе.
Включим отмену:
Promise.config({ cancellation: true });
Для задержки воспользуемся этим методом:
function delay(ms) {
return new Promise((resolve, reject, onCancel) => {
const timer = setTimeout(() => {
console.log('timer fired');
resolve();
}, ms);
onCancel(() => {
console.log('timer cancelled');
clearTimeout(timer);
});
});
}
Увидим timer fired
, A
и B
:
const source = delay(1000);
const consumerA = source.then(() => console.log(`A`));
const consumerB = source.then(() => console.log(`B`));
Увидим timer cancelled
:
const source = delay(1000);
const consumerA = source.then(() => console.log(`A`));
const consumerB = source.then(() => console.log(`B`));
source.cancel();
Увидим timer fired
и B
:
const source = delay(1000);
const consumerA = source.then(() => console.log(`A`));
const consumerB = source.then(() => console.log(`B`));
consumerA.cancel();
Увидим timer cancelled
:
const source = delay(1000);
const consumerA = source.then(() => console.log(`A`));
const consumerB = source.then(() => console.log(`B`));
consumerA.cancel();
consumerB.cancel();
И такое поведение является желаемым в большинстве случаев. У библиотеки нет причин полагать, что вызвав cancel на потребителе вы хотите полностью отменить операцию для всех потребителей. Она делает умнее, отслеживая количество активных потребителей и если их больше нет — распространяет отмену вверх по цепочке.
Важно, что каждый вызов then
или catch
возвращает новый промис связанный с исходным. Поэтому consumerA
не равен consumerB
и не равен source
. И поэтому увидим timer fired
и A
:
const source = delay(1000);
const consumerA = source.then(() => console.log(`A`));
const consumerB = source.then(() => console.log(`B`));
const consumerС = consumerB.then(() => console.log(`С`));
consumerС.cancel();
Насчёт fetch. Это не отмена промиса, а отмена нижележащей операции, в данном случае, сетевого запроса. Поэтому промис перейдёт в состояние rejected. Как вы правильно заметили, отмена bluebird-промиса не вызывает ни then, ни catch, что и является желательным поведением при отмене. Пользуясь этой фичей вы вообще не хотите ничего знать о том, как завершиться операция. Отмена через bluebird позволяет отменять именно промисы, а также нижележащие операции, если они такое поддерживают (таймеры, i/o с потоками и т.д.).
Это никак не принижает, а только дополняет, нововведения в fetch, если вы всё таки осмелитесь взять bluebird на клиента. Если нужно чтобы потребители получили уведомление о том, что промис уже ждать не надо, то это другой паттерн — либо timeout
либо race
.
bluebirdjs.com/docs/api/promise.longstacktraces.html
Bluebird: пояс с инструментами для асинхронщика