Comments 63
Хочу дополнить тему "Преобразование коллбэков в промисы"
Так как написано в статье, делать нынче уже не принято. Есть util.promisify
Первые два фрагмента кода отличаются
p.then(...);
p.then(...);
это не то же самое что
p.then().then()
Поэтому в первом случае a
, а во втором b
, никаких подвохов.
Не проще ли сразу воспользоваться вторым атрибутом then()?
При подходе promise.then(() => doSomething()).catch(...)
блок catch перехватит не только ошибки в оригинальном промисе, но и если что-то пойдет не так в doSomething
. Это бывает полезно.
Собственно, что и хотел сказать :)
PS: Понимаю, что перевод и «мопед не мой», но все-таки…
Предпочитаю использовать async/await. Но насколько я знаю, async/await всего лишь синтаксисический сахар над промисами, и ничто не мешает мне использовать Promise.all с функциями, которые async. Или я ошибаюсь?
let filenames = ['index.html', 'blog.html', 'terms.html'];
Promise.all(filenames.map(readFilePromise))
.then(files => {
console.log('index:', files[0]);
console.log('blog:', files[1]);
console.log('terms:', files[2]);
})
По-моему, использовать map в данном случае не лучшее решение — этот ведь синхронный перебор, и обработка файлов будет поочередной, а не параллельной. Или я не прав?
Забыли про генераторы и про то, что даже с нативной поддержкой промисы, bluebird быстрее и обладает чудесным богатым api. Пользуюсь им в разработке на восьмой ноде.
Из bluebird выкинуты многие полезные доработки, которые вошли в нативные.
Например, bluebird не сохраняет стек вызовов — удачи в ловле ошибок. Еще bluebird вызывает при необходимости продолжения синхронно — можно самому себе устроить состояние гонки даже в однопоточном языке.
Так что начинающим (а пост-то явно для них написан) я бы bluebird не рекомендовал.
delay, cancel, spread, promisify, timeout, inspection — там есть огромное количество фич, без которых, конечно, жить можно, но гораздо печальнее. В нативных, кажется, даже finally нет.
- Про promisify написано в первом же комменте этого треда
delay = promisify(setTimeout)
- spread — деструктуризация аргументов дает то же самое
Promise.all(...).then(([a,b,c]) => console.log(a,b,c))
- cancel — успешно решается на уровне библиотек. Например в axios это делается через токен.
- finally есть в обычном try/catch, который можно использовать в асинхронных функциях.
Если bluebird позволяет стрелять в ногу как говорит mayorovp, то я однозначно за нативные промисы, если нет очень крайней необходимости в скорости или других фишках bluebird.
Нее, delay = promisify(setTimeout)
работать не будет, порядок аргументов не тот. Тут надо вручную:
const delay = time => new Promise(resolve => setTimeout(resolve, time));
А вы попробуйте.
Нодовский promisify позволяет подменять возвращаемый результат через специальный символ. И для setTimeout этот кастомный символ уже определен
Про promisify написано в первом же комменте этого треда
Да я видел, но мне не нравится мне использовать его из util. Из Promise гораздо логичнее и чище.
cancel — успешно решается на уровне библиотек
Ага, например, на уровне request-promise это решается через cancel от bluebird. Зачем плодить лишние сущности?
finally есть в обычном try/catch, который можно использовать в асинхронных функциях.
Ээээ. Так мне нужно его в цепочке использовать, finally от обычного try catch вот ни разу не поможет. Или вы имели в виду — использовать его в связке с async/await?
C delay уже сказали, для timeout тоже надо будет какой-то костыль делать… Кстати, со стэком у меня как-то никогда не было проблем, а про race condition фразу я, честно говоря, не понял.
Многие забывают, что ответы могут приходить не в том же порядке, что и запросы
Вообще-то в catch войдет только синхронный вызов throw
api.getItem(1)
.then(item => {
delete item.owner;
//async throw
setTimeout(()=>{item.owner.name},0)
})
.catch(e => {
console.log("tt",e); // Cannot read property 'name' of undefined
})
встречал это непонимание концепции в различных библиотеках при ловле ошибок и ожидание попадания обработчика в catch
api.getItem(1)
.then(item => {
delete item.owner;
//async throw
return new Promise((resolve)=>{
setTimeout(()=>{item.owner.name; resolve()},0)
})
})
.catch(e => {
console.log("tt",e); // Cannot read property 'name' of undefined
})
Так? setTimeout это пример реализации сторонней, либо своей библиотеки в которой произошла ошибка при асинхронном вызове. Т.е. метод библиотеки вернул промис, но внутри он упал на ошибке.
Конечно же если чужая библиотека намеренно или случайно прячет от вас ошибку — ее достать не получится.
Но это просто означает что библиотека кривая.
Но наличие ошибки в библиотеке от промизов не зависит.
Проблема-то тут в том что в setTimeout передается функция, которая не перехватывает возможные исключения. И здесь уже совершенно не важно как именно вы не сможете получить об этом исключении информацию: через промиз, через колбек или через событие — вы ее все равно не получите.
Уточняю еще раз, код который я привел в пример встречается внутри библиотеки. Т.е не снаружи вызывается не правильно, а внутри в промисах ожидантся перехват ошибки в анонимной функции. И написал это лишь чтобы обратить на это внимание тех кто пишет статьи
Смотрите, вот вариант с колбеком:
api.getItem(1, (err, item) => {
if (err) return cb(err);
try {
delete item.owner;
setTimeout(()=>{ item.owner.name; cb(); }, 0);
} catch (e) {
console.log("tt",e);
cb(e);
}
});
Промизов нет, а ошибка — осталась.
function process(list) {
let promises = [];
promises.push(foo());
promises.push(bar());
list.forEach(() => promises.push(bar()));
return Promise.all(promises);
}
Вместо того чтобы вручную собирать все новые промисы, вызываю Promise.wait которая это делает за меня, код выше превращается в:
function process(list) {
return Promise.wait(() => {
foo();
bar();
list.forEach(bar);
});
}
function process(list) {
return Promise.all([
foo(),
bar(),
...list.map(bar)
]);
}
И что за метод такой Promise.wait? В документации про него ни слова нету.
class User {
constructor(raw) {
Object.assign(this, raw);
this.links.forEach(preload);
this.children = this.children.map((raw) => new User(raw));
}
}
// sync variant
var users = rawUsers.map((raw) => new User(raw));
// async variant
Promise.wait(() => {
return rawUsers.map((raw) => new User(raw));
}).then((users) => {});
Суть в том что есть некий синхронный код с глубоким стеком и в определенный момент где-то на глубоком уровне появилась асинхронная операция, и вам сверху нужно дождаться её завершения. Конечно можно начать конвертировать/рефакторить весь проект, превращать синхронные в ассинхронные/проброс промиса наверх, но выглядит это не очень. Простой отлов новых промисов выглядит куда приятнее (и по большей части работает как надо).И что за метод такой Promise.wait?Я написал, что это велосипед.
Конечно можно начать конвертировать/рефакторить весь проект, превращать синхронные в ассинхронные/проброс промиса наверх
Нужно начать рефакторить весь проект. В противном случае отхватите багов, потому что кто-то забудет поставить Promise.wait
. Или наоборот, поставит Promise.wait
внутри другого Promise.wait
.
Ну и покажите реализацию в коде, интересно посмотреть, какими хаками вы это сделали.
Нужно начать рефакторить весь проект.И можете не вписаться в дедлайны…
потому что кто-то забудет поставить Promise.waitА если кто-то забудет поставить Promise.all? Не пройдет тестирование и пофиксится. А вот если наченете рефакторить (а если там 500к кода?) то наделать ошибок шансов больше.
Или наоборот, поставит Promise.wait внутри другого Promise.wait.Работает как и ожидается.
какими хаками вы это сделали.Ничего сверх-естественного, например можно так в 15 строк (на проде вариант получше использую): jsfiddle.net/lega911/pvovavLe
PS: может вы зарефакторите мой пример выше? интересно посмотреть как сильно распухнет код.
Зачем так извращаться, если есть node-fibers, который делает ровно то, что вам надо?
Сайд-эффекты в конструкторе это уже плохо. У меня бы получилось как-то так
class User {
constructor(raw) {
Object.assign(this, raw);
this.children = this.children.map(c => new User(c));
}
load() {
return fetch(...).then(() => Promise.all(
this.children.map(c => c.load())
))
}
}
const users = rawUsers.map(user => new User(user));
Promise.all(users.map(user => user.load())).then(() => {
// что-то делаем с готовыми users
})
ну и совет от кэпа: если сразу делать нормально (имеется в виду всегда возвращать промис из асинхронных операций), то потом не придется переделывать, чтобы успеть в дедлайны.
Ну и по поводу вашей имплементации: а с нативными API, типа fetch
оно как работает?
Как я понимаю, они ваш переопредленный класс Promise не увидят, будут пользоваться стандартным.
Сайд-эффекты в конструктореСмотря что считать сайд-эффектами, вообщем это не аргумент.
У меня бы получилось как-то такКод сложнее (имхо), кода больше, лишняя функция («логика/апи»), итого большой реальный код может не слабо распухнуть.
если сразу делать нормально (имеется в виду всегда возвращать промис из асинхронных операций), то потом не придется переделывать, чтобы успеть в дедлайны.из асинхронных всегда возвращается промис.
Ну и по поводу вашей имплементацииЭто просто минимальный пример, у меня на проде (как я уже упомянул) другой вариант без подобных проблем.
Я не предлагаю отказываться от промисов, это просто ещё один подход.
Либа достаточно неплохо помогала бороться со всем этим callback-hell'ом и с неё было проще пересесть на промисы =) Не знаю даже, используют ли её сейчас или нет…
ЗЫ: В своих некоторых проектах, после каждого вызова есть обработчик ошибки, и логика парой не линейная, приходиться балансировать между «promisehill» и линейностью но код от этого не становиться читаем(С Async/await все более понятней).
async/await уже 4 года используется в языке C#, и там пока не собираются отказываться от него. Почему вы думаете что в js аналогичная функциональность уже через 2 года будет признана не самой удачной?
PS на async/await не надо "радостно рефакторить". Надо сразу писать с его использованием.
Промисы в ES6: паттерны и анти-паттерны