Pull to refresh

Comments 63

UFO just landed and posted this here

Первые два фрагмента кода отличаются


p.then(...);
p.then(...);

это не то же самое что


p.then().then()

Поэтому в первом случае a, а во втором b, никаких подвохов.

UFO just landed and posted this here
Но с чего автор взял, что в следующий колбэк не из цепочки придёт результат предыдущего?

Так ведь автор этого и не утверждает.

UFO just landed and posted this here
Не проще ли сразу воспользоваться вторым атрибутом then()?

При подходе promise.then(() => doSomething()).catch(...) блок catch перехватит не только ошибки в оригинальном промисе, но и если что-то пойдет не так в doSomething. Это бывает полезно.

UFO just landed and posted this here

Тогда я не понимаю, в чем дело.
Если расскажете подробнее, может придумаю, что ответить

Пробежал по диагонали. Первое, что пришло в голову — это поском по страницы поискать «async/await». На момент написания комментария нашел единственное упоминание в разделе ПОХОЖИЕ ПУБЛИКАЦИИ под названием «Async/await: 6 причин забыть о промисах»

Собственно, что и хотел сказать :)

PS: Понимаю, что перевод и «мопед не мой», но все-таки…

Ну, для того, чтобы полноценно пользоваться async/await понимать как работают промисы всё же необходимо.

добавьте опрос, кто использует промисы, кто коллбеки а кто async-await

Опять про node-fibers забыли :-(

И стримы (rx, bacon, kefir)

А как это оценивать? В одном проекте может понадобиться использовать все три подхода.

Спасибо за указанные тонкости и приемы работы с промисами. Теперь мой код станет чуточку лучше)
Предпочитаю использовать 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 в данном случае не лучшее решение — этот ведь синхронный перебор, и обработка файлов будет поочередной, а не параллельной. Или я не прав?
Запросы будут сделаны параллельно, а на выходе вы получите новый промис к-ый разрезолвится после того как придут ответы от всех промисов и будет содержать массив их ответов
Ага, спасибо! Буду знать.
В принципе пролистав статью ничего нового не увидел, но новичкам будет очень полезно. Автору совутую добавить сюда в паттерны yield'ы async'и и generator's)

Забыли про генераторы и про то, что даже с нативной поддержкой промисы, bluebird быстрее и обладает чудесным богатым api. Пользуюсь им в разработке на восьмой ноде.

Если мне не изменяет память, то нативные были в 5-7 раз медленее bluebird. Ускорение нативных в два раза будет маловато (кроме того, подозреваю, что bluebird при этом тоже ускорился). Свежих бенчмарков ни у кого нет?

Из 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 написано в первом же комменте этого треда

Да я видел, но мне не нравится мне использовать его из util. Из Promise гораздо логичнее и чище.


cancel — успешно решается на уровне библиотек

Ага, например, на уровне request-promise это решается через cancel от bluebird. Зачем плодить лишние сущности?


finally есть в обычном try/catch, который можно использовать в асинхронных функциях.

Ээээ. Так мне нужно его в цепочке использовать, finally от обычного try catch вот ни разу не поможет. Или вы имели в виду — использовать его в связке с async/await?


C delay уже сказали, для timeout тоже надо будет какой-то костыль делать… Кстати, со стэком у меня как-то никогда не было проблем, а про race condition фразу я, честно говоря, не понял.

q.then(() => this.loading = false);
this.loading = true;

Если продолжение будет выполнено синхронно — получится упс.

Как-то не возникало мысли так писать, гораздо логичнее


 q
.then(() => this.loading = true)
.then(() => doSmth())
.then(() => this.loading = false)
Не в защиту bluebird, но состояние гонки можно легко получить и в асинхронной модели.
Многие забывают, что ответы могут приходить не в том же порядке, что и запросы
UFO just landed and posted this here

Вообще-то в 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

UFO just landed and posted this here
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);
        }
    });

Промизов нет, а ошибка — осталась.

Такое ощущение что я проиив промисов что-то имею. Я пишу что catch вводит в непонимание разработчиков, которые ожидают волшебного поведения от catch. И это случилось после статей, в которых пишут что "теперь наступило счастье, про перехват ошибок можно забыть". Просто надо уточнять и разъяснять.

Незнаю насколько это антипаттерн, но кое где использую такой велосипед (foo, bar возвращают новые промисы):
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);
  });
}

Ваш код с Promise.all в ES6 можно переписать таким образом:
function process(list) {
  return Promise.all([
    foo(),
    bar(),
    ...list.map(bar)
  ]);
}


И что за метод такой Promise.wait? В документации про него ни слова нету.
Не, ну это же упрощенный пример, перепешите тогда такой вариант (preload возвращает промис):
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, который делает ровно то, что вам надо?

node-fibers хорош, но он не работает в браузерах. А для сервер сайда я использую другие инструменты.
python (и asyncio для асинхронщины), go, c/c++
мне этого хватает для большинства задач.

Сайд-эффекты в конструкторе это уже плохо. У меня бы получилось как-то так


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 и промисами был ещё один промежуточный этап в виде использования либы async (или даже логичней дать ссылку на более старую версию)

Либа достаточно неплохо помогала бороться со всем этим callback-hell'ом и с неё было проще пересесть на промисы =) Не знаю даже, используют ли её сейчас или нет…
UFO just landed and posted this here
С линейным выполнением промисов понятно(как показано в статье), а если необходимо ветвистая с обработкой ошибок на каждом этапе(просто выкинуть исключение круто, но чтобы продолжила программа работать дальше?). Как тут поступать?
ЗЫ: В своих некоторых проектах, после каждого вызова есть обработчик ошибки, и логика парой не линейная, приходиться балансировать между «promisehill» и линейностью но код от этого не становиться читаем(С Async/await все более понятней).
UFO just landed and posted this here
UFO just landed and posted this here

async/await уже 4 года используется в языке C#, и там пока не собираются отказываться от него. Почему вы думаете что в js аналогичная функциональность уже через 2 года будет признана не самой удачной?


PS на async/await не надо "радостно рефакторить". Надо сразу писать с его использованием.

Sign up to leave a comment.