Pull to refresh

Comments 54

чтобы избавится от await нужно перестать использовать await.

очень странная идея.
сперва выдумали проблемму(await нужен именно для этого «ада»)
потом просто решили исользовать старые добрые промисы.

а async/await — это и есть старые добрые промисы


и тут мы не отказываемся от await: мы просто используем его по-другому. А чтобы уметь использовать по-другому — см. п. 1 — надо знать, что это просто сахар вокруг промисов.


Хороший перевод хорошей статьи!

я понимаю, что await Promise.all(promises) использует await.
имхо проблемма надуманная.
есть масса мест где последовательные await логичны, да и для того они и придуманны.
Я бы на вашем месте почитал матчасть. async/await != Promise. Именно так. Не так уже пролетал перевод, с таким вот именно пониманием и очень корявыми примерами использования.

Мой друзья! Лучше использовать инструменты наподобие map библиотеки Bluebird, которые позволяют управлять количеством одновременно запущенных промисов:


Bluebird.map(
    array, item => doSomethingAsync(item), {concurrency: LIMIT}
)

Это защитит память приложения от переполнения при обработке огромных массивов и предотвратит возникновение блокировок.


Перевод моего комментария к этой статье.

Можно взять маленький модуль p-limit, и не тащить весь большой Bluebird.

>Это защитит память приложения от переполнения при обработке огромных массивов
Может сразу Stream api использовать?
Промисы по своей сути не особо подходят для больших обьемов данных.
// Задачу можно решить так, как показано выше, но я предпочитаю следующий метод (async () => { Promise.all([selectPizza(), selectDrink()]).then(orderItems) // асинхронный вызов })()

Разве здесь не нужен await (или убрать фигурные скобки вокруг тела функции)?


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


Как по мне, разобраться с этим уж точно не сложнее, чем с промисами. А код получается на порядки читабельнее.

У async/await совсем другие выявились недостатки. А именно необходимость заключать их в блоки try/catch которые с промисами выглядят порой немного лаконичнее как вызов функций then()/catch(). Без try/catch код выглядит действительно очень неплохо. C try/catch все опять становится весьма многоэтажно.

Все это погружать в Promise.all() конечно рационально но сразу теряется наглядность. Я где-то видел более естественное решение

const promiseSomeWhat = someWhat();
const promiseAnother = another();
const someData = await promiseSomeWhat;
const anotherData = await promiseAnother;
Один try/catch вместо кучи catch() — не знаю. Можно обойтись и одним catch()
Скорее всего один try/catch это просто само по себе плохо. Т.к. ошибки разные бывают.
кстати да. в яваскрипте с catch довольно плохо, нельзя ловить по типу
Я вот написал библиотеку with-error, оборачивающую функции с исключениями и многоэтажность исчезла. А вообще лучше не пользоваться в JS исключениями, крайне неудобная работа с ними, особенно в области типизации.
Ну во первых, зато нам доступен finnaly.
А во вторых вообще претензия не понятно, try catch стандартная конструкция js, которая десятки лет используется для синхронного кода. Если она плоха, то претензия к синтаксису js, более старому чем async/await.
Последний блок можно написать компактней:

const promises = items.map(async (item) => {
	await sendRequest(item);
});
await Promise.all(promises);
Можно и еще компактней
await Promise.all(items.map(sendRequest))

Потеряли return или лишние фигурные скобки.


Или бабель добавляет туда return сам? Просто в самой статье тоже была такая же ошибка.

А нужен ли там return?
Последняя строчка не ожидает результатов по завершению Promise.all
Автору оригинальной статьи уже сделали замечание по этому поводу и он принес свои извинения. Правда статью не откорректировал пока.

return нужен внутри map, иначе Promise.all ничего не дождётся.

Отсутствие return означает возврат Promise в данном случае.
Таким образом в promises попадет массив промисов, которые зарезолвятся в undefined после окончания соответствующих sendRequest.
Promise.all дождется факта окончания всех операций, и вернет массив undefined-ов, вместо результатов.
Зачем при рефакторинге этот вызов делать асинхронной функцией?
(async () => {
Promise.all([selectPizza(), selectDrink()]).then(orderItems) // асинхронный вызов
})()

Если уж сделали асинхронной, то почему бы тогда не дождаться резолва Promise.all с помощью await? Promise.all внезано тоже возвращает промис.
Сами себе каких-то проблем выдумали, сами их себе порешали.
Не увидел никакого ада, увидел неоптимальные блокировки.

+1. Вспомнил старый добрый make с его зависимостями, не маскирующимися под императивный код.

Очередной перевод статьи, автор которой не знает async/await, которому лень открыть тот же MDN и хотя бы посмотреть примеры готовки. Уже оставлял в недавнем вашем переводе коммент по этому поводу. async/await это не промисы, не вводите в заблуждение людей переводами таких статей.

Тут не нужено использовать Promise.all в принципе. Все await в рамках одного выражения отрабатывают параллельно, но никто чего-то этим даже не пытается пользоваться. В итоге мы имеем статьи вида: Чукча не читатель, чукча писатель.

Скорее всего будет работать нормально вариант
(async () => {
  const items = [
    choosePizza(await getPizzaData()),
    chosenDrink(await getDrinkData())
  ];
  [await addPizzaToCart(items[0]), await addDrinkToCart(items[1])]
  await orderItems()
})()

Код исправлен сильно в силу чрезмерной абстрактности.

Пример для одного из предыдущих переводов: repl.it/repls/AmusingZealousEnvironment
Измененный пример для работы с массивами: repl.it/repls/PoliteChocolateHashfunction

Ну и ответ: Ада не видел, видел г@#$%кодеров.
Скорее всего будет работать нормально вариант

Не совсем. В случае exeption будет Unhandled promise rejection вместо ожидаемого состояния onRejected. Try-catch тоже не сможет поймаль ошибку. Уже обсуждалось:

habrahabr.ru/post/326442/#comment_10175054

async/await это не промисы, не вводите в заблуждение людей переводами таких статей.


Ну как же не промисы. Если промисы. Асинхронная функция возвращает промисы. Всегда.
Оператор await ожидает промис. Об этом совершенно недвусмысленно говорит MDN.
developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Statements/async_function
developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Operators/await

Поправка: вот как раз оператор await ожидает не (только) промис, а любой объект с методом then.

Да это реально так. Толкьо что проверил. Но после этого скрипт не работет как ожидается. то есть функция then вяполняется но дальше конечно ничего ожидаемого не происходит. Кто-нибудь значет что-то об этом? Это баг?

try {

  var a = {};
  a.then=function(){console.log('then'); return 'not promise';};
  a.catch=function(){console.log('catch'); return 'not promise';};

  async function test() {
    await a;
  }

  Promise.resolve().then(async function(){
    var a = await test();
    console.log('a is', a);
  });
} catch(ex){
  console.log('error', ex);
}


Запускаем: node test.js
Вывод: then

Почему это так?
Потому что в then передается колбек который она должна вызвать когда настанет время. А вы его игнорируете.
Да с callback работает.
try {

  var a = {};
  a.then=function(resolve, reject){ console.log('then'); resolve('not promise');};
  a.catch=function(){console.log('catch'); return 'not promise';};

  async function test() {
    return await a;

  }

  Promise.resolve().then(async function(){
    var a = await test();
    console.log('a is', a);
  });
} catch(ex){
  console.log('error', ex);
}


Но это весьма неожиданно т.к. иногда в цепи промисов может оказаться объект и все работет как ожидается. Но если вдруг в его свойствах будет then то все перестанет работать. Конечно then не лучшее незвание для свойства. Но все же. Я это всего лишь имя своства и никакой магии от него не хочестся получать. Если Только это не специально созданный объект Promise. Утиная типизация какая-то получается.
Но если вдруг в его свойствах будет then то все перестанет работать

Да, есть такая засада. По сути система работает не с promise-ми а с любыми thenable объектами. И никаких Symbol.thenable для их идентификации не используется. Когда-нибудь с кем-нибудь это может сыграть злую шутку :)

UFO just landed and posted this here
Не надо так категорично.
async — значит что там может быть промис, а не должен. Это чисто семантическая вещь, которая позволяет писать await внутри, без этого вы получите ошибку компиляции.
Если справа от await будет не Promise — значение просто вернется напрямую.
Он даже нулл прокидывает.
Нет, как раз async означает что функция вернет промис. Это гарантируется по построению.
Давайте не будем столь категоричны.
В скриптовом языке далеко не все может быть гарантировано.
Вот тут все работает
function testable(x) {
    if (x > 10) {
        return new Promise(resolve => setTimeout(()=>resolve(x), 0));   
    }
    return x;    
}

async function test() {
    console.assert(await testable(1) === 1);
    console.assert(await testable(1000) === 1000);
    console.assert(await testable(null) === null);
}

test();

Не совсем понял что Вы имеете в виду.
Если функция имеет квалификатор async то она точно вернет промис. Всегда и во всех случаях.
Что имелось в виду подтвердить Вашим примером?

async f(){;}
f() — вернет промис
await f() — вернет undefined
Да, все правильно. Контекст неуловил.
Все await в рамках одного выражения отрабатывают параллельно, но никто чего-то этим даже не пытается пользоваться. В итоге мы имеем статьи вида: Чукча не читатель, чукча писатель.


Откуда эта информация? Ваш код рабочий но работает он не параллельно а последовательно. Пусть даже await будет помещен при фактических параметрах.
Так много пафоса однако. MDN:

> Цель функций async/await упросить использование promises синхронно и воспроизвести некоторое действие над группой Promises. Точно так же как Promises подобны структурированным callback-ам, async/await подобна комбинации генераторов и promises.
Все await в рамках одного выражения отрабатывают параллельно, но никто чего-то этим даже не пытается пользоваться.

Вранье. Вот только что попробовал в консоли Хрома:


> const foo = async x => {
    console.log("begin " + x);
    await new Promise(resolve => setTimeout(resolve, 100));
    console.log("end " + x);
    return x;
}
> [await foo(1), await foo(2)]
begin 1
end 1
begin 2
end 2
< (2) [1, 2]
> await Promise.all([foo(1), foo(2)])
begin 1
begin 2
end 1
end 2
< (2) [1, 2]
Кто-нибудь может объяснить, почему консоль не выдает ошибку SyntaxError в данном случае?

> [await foo(1), await foo(2)]
begin 1
end 1
begin 2
end 2
< (2) [1, 2]

Ведь оператор await используется не в контексте async функции.
Очевидно, потому что консоль работает «в контексте async функции».
Все это хорошо, но как решить, особенно в web приложении, кому поручить дождаться промис. Ведь есть соблазн делегировать его ожидание процессу, и тогда скрипач ( await ) не нужен.

И получается то, что у меня творится на web клиенте почты. Которая то повиснет, то не готова выполнять какие — то запросы, после первого запроса. Что же… выполняешь запрос во второй — третий — иногда четвертый раз. По мере того, как их «резиновая» виртуальная машина прогревает под мои запросы каши, они начинают работать.

И получается что пропустив await, его совсем даже не пропустили, а просто передали в руки конечного пользователя. Зато, я почти уверен, все показатели performance dashboard у разработчика пакета ПО зашкаливают.
Псевдосинхронность не добавит нам производительности.
Если мы захотели бы максимально оптимизировать работу программы, то разбили бы выполнение на воркеры(в ноде)

Попробуйте так же поиграться с классическими промисами, будет сложнее — эвэиты читаются проще, что даём нам возможность прикладывать меньше усилий для их группировки/оптимизации.
И, по моему скромному мнению главная проблема async/await — это try/catch hell с бесконечными unhandled promise rejection, который может возникнуть, например, если разработчики не договорились на каком уровне эти ошибки обрабатывать.
это try/catch hell с бесконечными unhandled promise rejection, который может возникнуть, например, если разработчики не договорились на каком уровне эти ошибки обрабатывать.


Вы хотели сказать — используют любую классическую библиотеку на основе промисов?
И, по моему скромному мнению главная проблема async/await — это try/catch hell с бесконечными unhandled promise rejection, который может возникнуть, например, если разработчики не договорились на каком уровне эти ошибки обрабатывать.

Ну если разработчики не договорятся на каком уровне обрабатывать ошибки, то в синхронном коде у них будет такая же проблема, так что это не проблема async/await.
Блин, при всём уважении, вся статья высосана из пальца. Автор бросается громкими словами вроде "проблемы с производительностью!!!", а по факту приведены банальные ошибки программиста, который зачем-то написал код, выполняющий последовательно вещи, которые можно делать параллельно. Проблема с забывчивостью при использовании await — не проблема синтаксической конструкции async/await, а проблема дизайна языка, которая тоже решается довольно просто — используйте TypeScript или другой компилируемый в JavaScript статически типизированный язык.
Вот оже самое хотел написать, статья абсолютно бестолковая. «Беда await ждёт пока завершится асинхронный вызов, не даёт перейти к следующей строке», так в этом этом его суть! Он так и должен работать. Если у кого-то с этим проблемы, то нужно учить его читать спецификацию, а не писать бестолковые жёлтые статьи.
Суть статьи заключается в том что просто кто то незнает как работает async/await. Подход async/await сильно упростил жизнь а то что есть дураки которые его используют неправильно, так они всегда были есть и будут, с тем же успехом можно взять любую фичу в любом языке найти её неправильное использование, обозвать это адом и сказать никогда ей не пользуйтесь.
А у автора исходной статьи аноды с катодами синхронность и асинхронность не перепутаны?
(Как-то так:
Заголовок спойлера
When you execute something synchronously, you wait for it to finish before moving on to another task.
When you execute something asynchronously, you can move on to another task before it finishes.
)
Более красивая итоговая реализация:
async function selectPizza() {
    let pizzaData = await getPizzaData();    // асинхронный запрос
    let chosenPizza = choosePizza();    // синхронный вызов
    await addPizzaToCart(chosenPizza);    // асинхронный вызов
}

async function selectDrink() {
    let drinkData = await getDrinkData();    // асинхронный запрос
    let chosenDrink = chooseDrink();    // синхронный вызов
    await addDrinkToCart(chosenDrink);    // асинхронный вызов
}

async function orderItems() {
    let items = await getCartItems();    // Товары в корзине
    let promises = items.map(sendRequest); // Получен массив горячих промисов
    await Promise.whenAll(promises);
}

let promises = [selectPizza(), selectDrink()];
await Promise.whenAll(promises);

await orderItems();   // асинхронный вызов

Исключения обработаем на внешнем уровне.

Полифиллы для AggregateError и Promise.allSettled():
class AggregateError extends Error {
    constructor(errors, message) {
        super(message);
        this.errors = errors && errors[Symbol.iterator] ? [...errors] : [];
    }
}

/**
 * Аналог черновика Promise.allSettled(). Не полностью соответствует Task.WhenAll()
 * Возвращаемый промис будет завершен, когда все из последовательности промисов будут завершены. 
 * Возвращаемый промис всегда будет завершаться в состоянии resolved, в отличие от Task.WhenAll()  
 * Это справедливо, даже если завершенные промисы будут находиться в состоянии rejected.
 * 
 * @returns {Array} Массив объектов {value, reason, status: "fulfilled" или "rejected"}
 * Свойства value или reason могут отсутствовать.
 */
if (!Promise.allSettled) {
    Promise.allSettled = promises => new Promise((resolve, reject) => {
        let inputTasks = Array.from(promises);
        let array = [],
            count = 0,
            len = inputTasks.length;
        for (let i = 0; i < len; ++i) {
            inputTasks[i].then(
                value => {
                    array[i] = { status: "fulfilled", value: value };
                    if (++count === len) resolve(array);
                },
                reason => {
                    array[i] = { status: "rejected", reason: reason };
                    if (++count === len) resolve(array);
                }
            );
        }
    });
}

Для красоты эмулируем Task.WhenAny() как надстройку над штатным методом Promise.allSettled() (можно и под другим именем, но пока не важно):
/**
 * Создать промис, который будет завершен, когда все из последовательности 
 * промисов будут завершены. 
 * Возвращаемый промис будет успешно завершен, если все промисы успешно завершены.
 * Результатом будет массив значений промисов.
 * Возвращаемый промис будет неуспешно завершен, если любой из промисов 
 * завершен неуспешно. 
 * Возвращенной ошибкой будет AggregateError c  массивом ошибок промисов.
 */
Promise.whenAll = promises => new Promise((resolve, reject) => {
    Promise.allSettled(promises).then(
        array => {
            let errors = array
                .filter(e => e.status === "rejected")
                .map(e => e.reason);
            if (errors.length)
                reject(new AggregateError(errors));
            else
                resolve(array.map(e => e.value));
        },
        error => { reject(error); }
    );
});
Sign up to leave a comment.