Comments 54
очень странная идея.
сперва выдумали проблемму(await нужен именно для этого «ада»)
потом просто решили исользовать старые добрые промисы.
а async/await — это и есть старые добрые промисы
и тут мы не отказываемся от await: мы просто используем его по-другому. А чтобы уметь использовать по-другому — см. п. 1 — надо знать, что это просто сахар вокруг промисов.
Хороший перевод хорошей статьи!
имхо проблемма надуманная.
есть масса мест где последовательные await логичны, да и для того они и придуманны.
Мой друзья! Лучше использовать инструменты наподобие map
библиотеки Bluebird, которые позволяют управлять количеством одновременно запущенных промисов:
Bluebird.map(
array, item => doSomethingAsync(item), {concurrency: LIMIT}
)
Это защитит память приложения от переполнения при обработке огромных массивов и предотвратит возникновение блокировок.
Перевод моего комментария к этой статье.
Может сразу Stream api использовать?
Промисы по своей сути не особо подходят для больших обьемов данных.
Так-то они тоже теперь на промисы переезжают. Поэтому вполне достаточно использовать Bluebird.map
или p-limit
, как посоветовали выше.
// Задачу можно решить так, как показано выше, но я предпочитаю следующий метод (async () => { Promise.all([selectPizza(), selectDrink()]).then(orderItems) // асинхронный вызов })()
Разве здесь не нужен await
(или убрать фигурные скобки вокруг тела функции)?
Статья как по мне спорная. Не увидел особого "ада", в основном, начальные примеры показывают, что бывает, если не понимаешь, как работает async/await
.
Как по мне, разобраться с этим уж точно не сложнее, чем с промисами. А код получается на порядки читабельнее.
Все это погружать в Promise.all() конечно рационально но сразу теряется наглядность. Я где-то видел более естественное решение
const promiseSomeWhat = someWhat();
const promiseAnother = another();
const someData = await promiseSomeWhat;
const anotherData = await promiseAnother;
А во вторых вообще претензия не понятно, 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 сам? Просто в самой статье тоже была такая же ошибка.
Последняя строчка не ожидает результатов по завершению Promise.all
return нужен внутри map, иначе Promise.all ничего не дождётся.
(async () => {
Promise.all([selectPizza(), selectDrink()]).then(orderItems) // асинхронный вызов
})()
Если уж сделали асинхронной, то почему бы тогда не дождаться резолва Promise.all с помощью await? Promise.all внезано тоже возвращает промис.
Сами себе каких-то проблем выдумали, сами их себе порешали.
Тут не нужено использовать 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.
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
Почему это так?
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 для их идентификации не используется. Когда-нибудь с кем-нибудь это может сыграть злую шутку :)
async — значит что там может быть промис, а не должен. Это чисто семантическая вещь, которая позволяет писать await внутри, без этого вы получите ошибку компиляции.
Если справа от await будет не Promise — значение просто вернется напрямую.
Он даже нулл прокидывает.
В скриптовом языке далеко не все может быть гарантировано.
Вот тут все работает
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();
Все await в рамках одного выражения отрабатывают параллельно, но никто чего-то этим даже не пытается пользоваться. В итоге мы имеем статьи вида: Чукча не читатель, чукча писатель.
Откуда эта информация? Ваш код рабочий но работает он не параллельно а последовательно. Пусть даже await будет помещен при фактических параметрах.
> Цель функций 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]
И получается то, что у меня творится на 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.
(Как-то так:
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); }
);
});
Побег из ада async/await