Комментарии 182
Мне кажется, что комитет слишком увлекся синтаксическими плюшками в ущерб стандартной библиотеке. async/await это здорово, конечно, но для вменяемой работы с датами надо до сих пор ставить сторонний пакет, а ведь это гораздо проще стандартизировать и полифиллить, чем новый синтаксис. Проблемы с left-pad можно было легко избежать, если бы этот метод был в стандартной библиотеке.
Нет. На ES6+ писать намного приятней, чем на ES5. Порог вхождения, правда, существенно выше
> Проблемы с left-pad можно было легко избежать, если бы этот метод был в стандартной библиотеке
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
На ES6+ писать намного приятней, чем на ES5.
Кто спорит?
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
Это можно и нужно было сделать еще в ES5, а попадет это только в ES2017.
Да? И как там вывести дату в формате DD.MM.YYYY?
Date().replace(/(.+?)(\w{3}) (\d+) (\d{4})(.+)/,'$3.$2.$4')
ну и там строчку в число перевести массивом немного: )let date = new Date();
date.toLocaleString('ru-RU', { day: 'numeric', month: 'numeric', year: 'numeric' });
Офигенное API. Но вроде бы работает, справедливости ради.
Видите ли в чем дело — поставить сторонний пакет проще чем "поставить" синтаксическую плюшку. Поэтому синтаксичкские плюшки — это в языке самое важное.
Для работы с датами стандартом de-facto стал moment.js, несмотря на корявость своего API (особенно его любовь мутировать даты). Меня уже давно мучает вопрос, почему просто не взять и не включить его как стандарт? Доработать API и красота.
Не уверен, что одобряю эту идею. Помнится, были предложения включать jQuery прямо в состав браузера — мол, все равно 95% сайтов его использует. Никто не стал этого делать, и слава богу.
даже переменная в некоторых браузерах та же ($).
Разве?
А также classList, map, each, Promises, formData и fetch, css анимации, да в общем-то почти все в том или ином виде застандартизировали, и слава богу, что не под копирку, только про цепочки вызовов забыли.
Возьмите date-fns :)
Зачем нужег await all если можно написать await Promise.all(...)?
async это лишь синтаксический сахар для промисов
Джаваскрипт по-особому отрабатывает await в циклах и условных операторах — с промисами такого поведения нет — поэтому это нечто большее, чем синтаксический сахар.
async это лишь синтаксический сахар для промисов
нечто большее, чем синтаксический сахар.Я в кишках V8 не копался, но у меня есть стойкое подозрение, что
async/await
работает поверх генераторов с резолвом за-yield
-енного промиса при прогоне результирующего итератора. Результат .catch
же скармливается .throw
. Слишком уж «гибкий» для промисов флоу получается, на них такого без бубнов не сделать, взять те же циклы.Посмотрите, во что бабель транспайлит async/await с включенным transform-async-to-generator. Ну очень похоже, что V8 делает то же самое, при чем на том же JS, как в случае с промисами.
async/await
://исходник с разными вариантами async/await
const delay = ms => new Promise(resolve => {
setTimeout(() => resolve(ms), ms);
});
const throwAsync = (error, ms) => new Promise((resolve, reject) => {
setTimeout(() => reject(new Error(error)), ms);
})
const foo = async (n, shouldThrow) => {
for (let i = 1; i < n + 1; i++) {
const result = await delay(i * 1000);
console.log('wait', result);
}
if (shouldThrow) {
await throwAsync('Should throw', 1000);
}
return 'done';
}
foo(3).then(::console.log).catch(::console.error);
foo(3, true).then(::console.log).catch(::console.error);
//результат после бабеля с transform-async-to-generator
var _context;
function _asyncToGenerator(fn) {
return function() {
var gen = fn.apply(this, arguments);
return new Promise(function(resolve, reject) {
function step(key, arg) {
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(function(value) {
step("next", value);
}, function(err) {
step("throw", err);
});
}
}
return step("next");
});
};
}
const delay = ms => new Promise(resolve => {
setTimeout(() => resolve(ms), ms);
});
const throwAsync = (error, ms) => new Promise((resolve, reject) => {
setTimeout(() => reject(new Error(error)), ms);
});
const foo = (() => {
var _ref = _asyncToGenerator(function*(n, shouldThrow) {
for (let i = 1; i < n + 1; i++) {
const result = yield delay(i * 1000);
console.log('wait', result);
}
if (shouldThrow) {
yield throwAsync('Should throw', 1000);
}
return 'done';
});
return function foo(_x, _x2) {
return _ref.apply(this, arguments);
};
})();
foo(3).then((_context = console).log.bind(_context)).catch((_context = console).error.bind(_context));
foo(3, true).then((_context = console).log.bind(_context)).catch((_context = console).error.bind(_context));
Уделите внимание функции
_asyncToGenerator
, как она работает с промисами, принимаемыми из step("next")
. Еще посмотрите, как все await
заменились на yield
абсолютно в тех же местах без перекомпоновки кода, что пришлось бы делать при переписывании на промисы.Исходный посыл был к тому, что есть очень большие подозрения, что async/await внутри V8 сделан не на основе промисов, а как раз на основе генераторов.
Поиграться можно вот с таким набором пресетов.
Исходный посыл был к тому, что есть очень большие подозрения, что async/await внутри V8 сделан не на основе промисов, а как раз на основе генераторов.
Почему вы разделяете эти два случая?
async это лишь синтаксический сахар для промисовДействительно, промисы и генераторы работают в тандеме в случае async/await. Async функция — это возвращаемый промис, а await точки — yield в генераторе.
Да, но зато можно определить через символы является ли функция async:
var fn = async function () {};
fn[Symbol.toStringTag] === "AsyncFunction";
Ну и в чём прикол? В том что они реализованы на уровне движка js? Какая разница, они все реализованы одинаково по смыслу, просто так они реализованы в движке, а так их нужно реализовывать самим.
Через символы можно много чего определить, в том числе и генераторы:
(function * foo() {})[Symbol.toStringTag] === 'GeneratorFunction'
Другое дело, что смысловой нагрузки это никакой не несет.
Пример использования — https://github.com/caolan/async/pull/1390
async/await
существует для того, чтобы его можно было задетектить через Symbol.toStringTag
. Генераторы тоже детектятся, а решают те же задачи и даже чуть больше.И если уж вы используете стороннюю библиотеку, то хелпер для «размотки» генератора есть в co.
Нет никакого противоречия. Когда метод в конечный автомат или генератор превращает транслятор — это нормально. Когда то же самое делает программист вручную — это пляски с бубном и костылями.
Не знаю, как в JS, а в C# за асинхронными функциями стоит довольно суровая кодогенерация. Грубо говоря, каждая функция разворачивается в класс, переменные внутри функции — в поля класса, а ветки выполнения, содержащие await — либо в отдельные функции, либо в функцию с большим switch (можно и так, и так реализовывать машину состояний).
Можно этот класс написать вручную, тогда код с использованием async/await будет идентичен промисам.
Так что async/await — это именно сахар.
let a = await _a()
let b = await _b()
let c = await _c(a, b)
console.log(c)
В мире с промисами это будет выглядеть как-то вот так:
Promise.all(_a(), _b())
.then(r => _c(r[0], r[1]))
.then(console.log)
Может быть и ничего так, но гораздо сложнее для чтения и понимания, а если между строками ставить еще какие-нибудь логи или вызовы синхронных функций, то код на промисах станет гораздо сложнее.
В C# эти страшные «промисы» (Task) резолвит внутренний оптимизатор, по сути оно во что-то такое и превращается, но пользоваться этим гораздо удобнее, имхо.
Нет. Ваш пример с await с обычными промисами выглядел бы так:
_a()
.then(a => _b().then(b => _c(a, b))
.then(console.log);
То есть еще хуже, чем вы написали. Сначала выполнится промис _a, и только потом — _b.
Чтобы промисы работали синхронно, действительно нужно использовать Promise.all:
let [a, b] = await Promise.all([_a(), _b()]);
let c = await _c(a, b);
console.log(c);
Необязательно. Можно же и вот так сделать:
let a = _a();
let b = _b();
let c = _c(await a, await b);
console.log(await c);
Но с тем, что первый пример был некорректен — согласен.
Нельзя. Сделаем такой простенький промис для демонстрации:
const p = (a, b) => new Promise(resolve => setTimeout(resolve, 1000, b ? { a, b } : a));
Вопрос на засыпку: когда в консоли появится результат — через секунду, две или три?
(async () => {
console.log(await p(await p(1), await p(2)));
})()
Или более развернуто (как у вас):
(async () => {
let a = p(1);
let b = p(2);
let c = p(await a, await b);
console.log(await c);
})()
Вы изменили мой код! Он должен выглядеть вот так:
(async () => {
let a = p(1);
let b = p(2);
console.log(await p(await a, await b));
})()
Результат в консоли появится через 2 секунды. А приведенный вами код покажет его через 3 секунды.
Вообще, развернутый вариант у меня в комментарии тоже за 2 секунды отработает.
Причины такого поведения понятны, но совершенно (ИМХО) не очевидны. Попробую использовать вопрос о разнице между этими двумя кусками кода и её причинах на собеседованиях =)
То есть получается, там, где я сказал "нельзя", на самом деле можно. Довольно-таки глупо с моей стороны не заметить, что оба промиса (a и b) запускаются сразу друг за другом.
>Однако, например, в C# подобная функциональность есть уже многие годы, и те, кто с этим знакомы, знают, что польза от неё стоит временных неудобств при чтении кода.
Я не луддит мне просто не очевидно зачем менять один синтаксис на другой.
Второе, все приведенные «плюшки» совсем меркнут если использовать bluebird – он очень улучаешт работу с ошибками и дает кучу утилит для координации промисов.
тут предлагают использовать async/await и забыть о промисах делая чуть короче простые примеры. Но ничего не сказано про более сложные кейсы – что делать с типизацией ошибок? как координировать несколько промисов? все это решает bluebird
второе, статья манипулятивна:
const makeRequest = () => {
return promise1()
.then(value1 => {
// do something
return Promise.all([value1, promise2(value1)])
})
.then(([value1, value2]) => {
// do something
return promise3(value1, value2)
})
}
так выглядит намного короче
const makeRequest = () => promise1()
.then(value1 => Promise.all([value1, promise2(value1)]))
.then(([value1, value2]) => promise3(value1, value2))
а с блюбердом совсем так:
const makeRequest = () => promise1()
.then(value1 => [value1, promise2(value1)])
.spread((value1, value2) => promise3(value1, value2))
>Движение языка в сторону упрощения
js и так простой, то что с этой «фичей» ассинхронный код начинает выгдлядеть как синхронных – сомнительный плюс.
код ниже не имеет смысла – с тем же успехом можно писать синхроно и не заморачиваться и есть вероятность что даже быстрее будет
const makeRequest = () => {
return callAPromise()
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => callAPromise())
Ну в статье довольно понятно какие плюсы. Из каллбеков к примеру ретурн не сделать или трайкатч, потому что вся информация о стеке уже будет потеряна.
Я сам по началу не разобрался и думал нафига козе баян, а код не просто выглядит как синхронный, он им и является, потому что стек сохраняется отсюда все плюсы.
>он им и является, потому что стек сохраняется отсюда все плюсы.
вы уверены в своих словах? в чем профит? юзайте все что *sync из стандартной библиотеки – будет вам счастье.
Вам никто не мешает использовать async/await и bluebird вместе.
Например:
const makeRequest = async () => {
console.log(await getJSON())
return "done"
}
//против:
const makeRequest = () =>
getJSON().then(console.log)
.then(()=> 'done')
async () => {
let t1 = await call();
let t2 = await call();
let t3 = await call();
}
Я верно понимаю, что в примере выше call() будет вызываться асинхронно, и не дожидаться завершения предыдущих вызовов?
Нет. Каждый await ожидает завершения прежде чем передать управление дальше.
Чтобы было как вы написали — надо делать так (пишу по памяти, могу напутать с синтаксисом немного):
var [f1, f2, f3] = await Promise.all([call(), call(), call()])
Но сам кусок написан так что call'ы будут выполнятся по очень – если нужно условно одновременно, то нужен Promis.all как mayorvp написал
как вариант без Promise.all
async () => {
let t1 = call();
let t2 = call();
let t3 = call();
let f1 = await t1;
let f2 = await t2;
let f3 = await t3;
}
Очевидно, что то что и написал, код выше эквивалентен этому
var [f1, f2, f3] = await Promise.all([call(), call(), call()])
Не эквивалентен, т.к. try-catch с таким кодом не работает, реально будет поймано исключение только для первого await, остальные уйдут в "Unhandled promise rejection" (с Promise.all такой проблемы не будет). В соседнем топике обсуждалось — https://habrahabr.ru/post/326442/#comment_10177554
Последовательно, опять же… А нужно именно паралельное. Допустим t1 будет идти 7 мс, t2 — 3 мс, t3 — 12 мс. Если сделать через Promise.all()
, то даже если мы будем учитывать, что на вызов call уходит само по себе 1 мс, получится всего max(12, 7, 3) + 3 = 15 мс, в отличии от ваших последовательных 7 + 1 + 3 + 1 + 12 + 1 = 25 мс. Мне кажется, что преимущество Promise.all()
достаточно очевидно.
вы не поверите, но таки пресдтавленный maxxon код «почти»(с оговорками для ноды) паралельный и медленнее того который с Promise.all на ~2ms – и то, есть вероятность, что это лаг, а не реальное замедление
Другой вопрос – что это не очевидно, и синкаксически его код не показывает что он «паралельнный»
Блин… Согласен, не досмотрел. Ведь промисы все просто в переменных сохраняются, а не генерятся на самом await...
Но то, что это совершенно не очевидно — 100%.
Другой вопрос – что это не очевидно, и синкаксически его код не показывает что он «паралельнный»
call()
— запуск запроса, а await — ожидания его выполнения. То есть сначала запускается три запроса и только потом запускается их ожидание. Мне было вполне очевидно, что они исполняются параллельно. Видимо, необходимо базово понимать, как этот код работает.Ну, тут проблема не столько в оператре await, сколько в функции call. По названию не видно что она асинхронная.
В статье ни слова про это: https://habrahabr.ru/post/320306/
Вопрос такой: как с помощью async/await организовать тот же самый Promise.all()
? Ведь в таком коде
let a = await first(),
b = await second();
Второй промис будет выполнен только после того, как выполнится первый. А ведь второй от первого не зависит, так что такой вот код сделать нормальным можно только вот так вот:
let [a, b] = await Promise.all([first(), second()]).then((a, b)=> [a, b]);
А это как-то не сильно входит в идею синтаксического сахорочка...
Как показали тесты .then()
в данном случае не является нужным… Но всё равно конструкция получается не сильно сладкая.
https://habrahabr.ru/company/ruvds/blog/326074/#comment_10164346
Но, честно говоря, с Promise.all как-то интуитивней становится. А то слишком замороченно получается.
Да вообще-то можно было бы вообще для всего промис интерфейса сделать сахарок, например логические операторы для промисов) Это было бы вообще офигительно нечитаемо.
let [a, b] = await [ first(), second() ];
Не согласен. потому что по идее эта конструкция должна выглядеть так:
let [a, b] = [await first(), await second()];
Ведь массив не является промисом… А в качестве синтаксического сахарка принимать массивы…
Ну это получается нужно ещё и такую вещь реализовывать:
let {first: a, second: b} = await {first: first(), second: second()}
Да и что тогда делать, например с такой вещью?
let [a, b] = await [1, second()]
Ведь такое вполне может произойти… Promise.all()
с этим справляется, но вот реализовать такое на синтаксическом сахаре будет уже много менее явно и понятно со стороны.
let {first: a, second: b} = await {first: first(), second: second()}
Это валидно, остальное пусть валится с ошибкой.
Хорошо так придумано! То есть Promise.all()
может, а синтаксический сахар к нему пусть с ошибкой валится...
Тогда без объектов… Тогда не весь изначальный синтаксический сахар присвоения значений получается… Короче говоря дилемма.
Понимаете ли, в этом то и прикол, что нужно либо добавлять объекты внутрь Promise.all()
, что не сильно влечёт за собой проблемы, либо не делать этой прикольной штуки для объектов, либо сделать ещё умнее, сделать await
отдельным оператором, который принимает только промисы, а остальные значения возвращает через Promise.reslove()
, как собственно и сделали они)
Да, должна быть обратная совместимость, т.е. код, который раньше использовал промисы легко переключается на эвейт, но это не значит, что мы должны слепо следовать устаревшим контрактам.
На счёт поддержки старого у меня почему-то вспоминается сразу ie… Не знаю почему, но ассоциация у меня прям жёсткая, когда слышу/читаю слово "совместимость"…
А на счёт того, что должны или нет, тк вопрос в том, что js сам по себе начинает поддерживаться по кускам, поэтому сейчас есть смысл вводить только сахарок, который можно будет переделать на старые версии. Тут это логично, потому что есть до сих пор люди, которые сидят на ie7-9, которым про наши холивары "что лучше, Promise или await/async" вообще пофигу, у них не работает ни то, ни другое по дефалту.
Промисы — костыль своего времени из-за отсутствия евейта.
Нет, это не так. Невозможно сразу сделать оператор await, не вводя в язык промисы или их аналоги.
На счёт as
они просто попытались спереть у питона… Но у них не получилось, потому что модули в js всё равно редатировать труднее, чем питоновские, там потому что имя модуля, из которого всё берётся находится раньше вещей, которые из него берутся, что в insert mode ещё хоть как-то можно понять. А вот когда тебе кроме того, что изменить импорты нужно ещё и путь к модулю запоминать, вот тут уже начинается портативный ад.
Глядя на некоторые из этих примеров использования промисов и правда может разболеться голова, ведь промисы как раз таки и были задуманы для решения callback-hell и этого адского уровня вложенности.
Но некоторые из них же можно переписать!
например, вместо
const makeRequest = () => {
return getJSON()
.then(data => {
if (data.needsAnotherRequest) {
return makeAnotherRequest(data)
.then(moreData => {
console.log(moreData)
return moreData
})
} else {
console.log(data)
return data
}
})
}
я бы написал
const makeRequest = () => {
const requestPromise = getJSON().then((data) => {
return data.needsAnotherRequest ? makeAnotherRequest(data) : data;
});
requestPromise.then(console.log);
return requestPromise;
}
а вместо
const makeRequest = () => {
return promise1()
.then(value1 => {
// do something
return promise2(value1)
.then(value2 => {
// do something
return promise3(value1, value2)
})
})
}
сделал бы
const makeRequest = () => {
const p1 = promise1();
const p2 = p1.then((value1) => promise2(value1));
const p3 = Promise.all([p1, p2]).then(([value1, value2]) => promise3(value1, value2));
return p3;
}
но с другой стороны, не могу не согласиться, что с async/await это выглядит поопрятней =)
Но вот такое:
async function gogogo(queryList) {
let resultJson = {}
for (const query of queryList) {
let json = {}
const type = await getType(query)
if (type === 1) {
json = await getJson(query)
}
else if (type === 2) {
const rawData = await getRawData(query)
json = await rawToJson(rawData)
}
else {
break
}
Object.assign(resultJson, json)
}
return resultJson
}
async function doSomething() {
const resultJson = await gogogo(queryList)
const html = templateGenerator(resultJson)
console.log(hmtl)
}
doSomething()
Уже становится немного сложновато представить на промисах, особенно если какой-то еще логики надо добавить
const Promise = require('bluebird')
function gogogo(queryList) {
return Promise
.resolve(queryList)
.map(getType)
// can be incapsulated in a function but original sample doesn't do that
.map(function(type) {
if (type === 1) {
return getJson(query)
}
else if (type === 2) {
return getRawData(query)
.rawToJson(rawData)
}
else {
return {}
}
})
.reduce(Object.assign, {})
}
function doSomething() {
gogogo(queryList)
.then(templateGenerator)
.tap(console.log)
}
doSomething()
Хорошо, это или плохо – не знаю, это тема для извечного холивара. Но мне все же кажется, что async/await – позволяет писать в псевдо-синхронной манере игнорируя понимаю когда произойдет асинхронная операция
ну и заодно интересно – есть ли щанс достичь аналогичного поведения с async/await? с concurrency = Infinity или, например, concurrency = 10
Есть у вас массив урлов. Запрос по каждому из них возвращает ключ, и каждый, начиная со второго, принимает в запросе ключ, полученный от предыдущего запроса. Количество урлов не известно. Нужно получить последний ключ. И давайте договоримся, всевозможные хелперы из всяких bluebird и компании использовать нельзя.
Кроме того, начните сначала с себя. Ваш комментарий без кода выглядит, мягко говоря, голословно
Неиспользовать технологии – это луддизмМы как раз обсуждаем технологию: async/await или промисы. Не надо тащить сюда ворох библиотек, облегчающих работу с промисами и инкапсулирующих бойлерплейт.
Вот вам пример.
declare const get: (url: string, key: string) => Promise<string>;
const getKey = async (initial: string, urls: string[]): Promise<string> => {
let result = initial;
for (const url of urls) {
result = await get(url, result);
}
return result;
};
PS. простите мне мой TS
одну и странно слышать это от человека предоставивщего пример на typescript
const Promise = require('bluebird')
const getKey = (initial, urls) =>
Promise.resolve(urls)
.reduce(
key, url => get(url, key),
initial
)
const getKey = async (initial, urls) => {
let result = initial;
for (const url of urls) {
result = await get(url, result);
}
return result;
};
Что вы везде этот bluebird тащите? Можете руками написать асинхронный редьюс?
очевидно – нормальная обработка ошибок и приятные фичи
>Можете руками написать асинхронный редьюс?
думаю смогу, но давайте не будем это проверять. Потому что я попрошую взамен гарантий что вы сможете руками добавить в язык async/await
Кроме того, начните сначала с себя.
думаю смогу, но давайте не будем это проверять.
Вы что-то мечетесь, неужели так сложно без bluebird?
Потому что я попрошую взамен гарантий что вы сможете руками добавить в язык async/awaitОни уже в языке и спеке.
reduce тоже есть в языке и в спеке.
А если серьезно, давайте вы мне не будете расказывать – что я должен, а что нет.
>неужели так сложно без bluebird?
ага, а еще без underscore, jquery и еще вагона библиотек, которые в той или иной мере вошли в язык, Browser API
На этом – давайте закончим дискуссию, а то она несколько в другую плоскость перерешла.
Так что по рукам или слились? вы берете рантайм от 51-го Firefox и допиливаете туда async/awaitЭто вы слились. Вы тащите стороннее апи для расширения существующей спецификации только для того, чтобы упорно пытаться решить задачу, для которой в язык введен отдельный инструмент, не используя этот инструмент. Главное преимущество async/await и генераторов — возможность удобно работать с циклами. Вы так здорово приводите примеры на промисах в ситуациях, когда с их помощью действительно можно решить поставленную задачу, а примера с циклом я от вас так и не дождался.
Ну что ж, действительно, закончим.
очевидно – нормальная обработка ошибок и приятные фичи
То есть, встроенные в язык Промисы по вашему — не юзабельны?
ну вот без await
const getKey = (prevKey, remainingUrls) => {
if (!remainingUrls.length) {
return Promise.resolve(prevKey);
} else {
return getKeyFromApi(remainingUrls[0]).then(newKey => {
return getKey(newKey, remainingUrls.slice(1))
});
}
}
насколько я помню, в случае асинхронных операций, стек не съедается таким образом (я даже проверил это для 18000 урлов с текущей глубиной стека в 17800)…
где-то даже видел "хак" в виде tail-call optimisation, когда себя же вызывают через setTimeout)
вот по памяти, я думаю, будет не очень оптимально, это да.
Чтобы было оптимально по памяти — надо вместо slice использовать растущий индекс. Получится даже оптимальнее чем через reduce.
там кроме слайсов создаётся ещё куча замыканий в виде анонимок, вызывающих эту функцию с новыми параметрами)
Спецификация промизов явным образом требует от рантайма чтобы колбек, переданный в then, запускался на пустом стеке (формально — не содержащем никаких пользовательских фреймов).Про спеку и пустой стэк не знал, спасибо. Я не особо силен в этой внутрянке, но разве аргументы функции не образуют новый скоуп, который где-то должен лежать, даже при очистке стэка? Это уже к вопросу расхода памяти.
Цепочка хранимых скоупов не может быть длиннее чем вложенность кода в редакторе и не зависит от глубины вызовов.
И таких цепочек хранится всего одна — для следующей итерации.
Цепочка хранимых скоупов не может быть длиннее чем вложенность кода в редакторе и не зависит от глубины вызововСтоп, как это не зависит от глубины? Вам в рекурсивную функцию в каждом вызове приходят новые значения, они должны храниться.
Но это каждый раз будет новая цепочка, с той же самой глубиной.
Вы так говорите как будто await ничего не выделяет при вызове :-)
Опять двадцать пять… Почему вы считаете рекурсию безусловно хуже цикла, если она не требует больше памяти и не ест стек?
Но это каждый раз будет новая цепочка,Старая куда денется? Уничтожится? Зачем наматывать эти цепочки рекурсивно, когда можно просто пройтись в рамках одного скоупа циклом по массиву, сохраняя ссылку на ключ?
Сравните 2 примера выше — в рекурсивном варианте вы выделяете память каждый раз заново для всей начинки getKey при вызове. Слайс массива — это вообще надругательство.
Про слайс массива я уже тоже писал, его надо на переменную с индексом заменить или на использование итератора.
Прошу обратить внимание, что все существующие js-реализации async/await используют рекурсивный вызов на каждый оператор await. И нет оснований предполагать что рантайм браузера будет реализован сильно оптимальнее.
А вот это я с трудом представляю как изобразить, чтобы такой вариант заработал:
return getRawData(query)
.rawToJson(rawData)
При то, что и getRawData и rawToJson асинхронные функции. Либо тут надо быть гуру промисов, чтобы в этом разобраться
Но даже при этом всё равно получилось не то же самое, так как «return {}» не тоже самое что «break», и в случае с map перебор продолжится, а в моей случае предпологался выход из цикла
Либо тут надо быть гуру промисов, чтобы в этом разобратьсяНет, это просто опечатка, должно быть
getRawData(query).then(rawToJson)
return getRawData(query)
.then(rawToJson)
>Но даже при этом всё равно получилось не то же самое, так как «return {}» не тоже самое что «break», и в случае с map перебор продолжится, а в моей случае предпологался выход из цикла
как-то это не логично чуть-чуть(точно не continue)
но ок:
const Promise = require('bluebird')
function gogogo(queryList) {
return Promise
.resolve(queryList)
.map(getType)
.then((types) =>
types.find(x => x !=== 1 && x !== 2)
)
.map(function(type) { // can be incapsulated in a function but original sample doesn't do that
if (type === 1) {
return getJson(query)
}
else {
return getRawData(query)
.rawToJson(rawData)
}
})
.reduce(Object.assign, {})
}
function doSomething() {
gogogo(queryList)
.then(templateGenerator)
.tap(console.log)
}
doSomething()
>функционал сторонней библиотеки
мы, вроде бы, обсуждаем фичу которая почти везде возможна только с транспилером. Давайте, если на то пошло откажемся от всех библиотек сразу тогда. Представьте как будет интересно – все руками. Заодно и денег будем больше рубить с любого проекта.
Будет как 10 лет назад: «Ищу php-разработчика со своим фреймворком – любителей писать с нуля, просим не беспокоить»
Давайте, если на то пошло откажемся от всех библиотек сразу тогда. Представьте как будет интересно – все руками.И смешались в кучу кони люди. Не путайте часть спецификации языка и сторонние библиотеки.
Async/await не требует никаких библиотек для работы. Генераторы почти не требуют никаких библиотек для работы. Почти, потому-что можно раннер либо самому написать, либо взять из бабеля (в комментах выше), либо из co.
мы, вроде бы, обсуждаем фичу которая почти везде возможна только с транспилером.Раз, два, три.
мы, вроде бы, обсуждаем фичу которая почти везде возможна только с транспилером
Нее, мы обсуждаем фичу которая нативно работает в node, и во всех актуальных браузерах, кроме Edge и Opera Mini
http://caniuse.com/#feat=async-functions
как-то это не логично чуть-чуть(точно не continue)
но ок:
Ну вот, про это и речь, «Уже становится немного сложновато представить на промисах, особенно если какой-то еще логики надо добавить»
Всё становится очень хрупким и нужно всю цепочку держать в голове, чтобы небольшие изменения внести. При этом никто не говорит, что это не возможно, возможно-то возможно
Дальше – рассуждайте сами, какой подход вам более покажется преемлемым
вопрос исключительно в том что стоит показывать равносильные примеры, а не те в которых один подход явно выгодней другого, но это не к вам вопрос – а к статье
Всё становится очень хрупким и нужно всю цепочку держать в голове, чтобы небольшие изменения внести. При этом никто не говорит, что это не возможно, возможно-то возможноДостаточно чуть усложнить условие (приложение в процессе динамического развития, много экспериментов):
const type = await getType(query)
const subtype = await getSubType(query)
if (type === 1 && subtype === 4) {
json = await getJson(query)
}
else if (type === 2 && !subtype) {
const rawData = await getRawData(query)
json = await rawToJson(rawData)
}
else if (subtype === 1) {
break
}
И всё становится чуточку сложнее и нужно поломать голову, как это впихнуть в промисы без промисс-хелла.
И я уверен, что это получится сделать, но я не уверен, что можно это сделать за 10 секунд, как это делается на async-await
Но забыть про промисы нельзя хотя бы потому, что с помощью промисов удобно оборачивать функции на коллбэках в асинхронные функции.
Вот зачем вы пишите Promise.resolve(queryList).map(getType)
— когда можно написать queryList.map(getType)
? И будет работать без сторонних библиотек.
Вы же сначала сами переусложняете код — а потом жалуетесь что вас не понимают.
PS дихотомия между отверткой и молотком — ложная. Нужны оба инструмента!
function gogogo(queryList) {
const items = queryList.map(query => {
const type = await getType(query);
if (type === 1) {
return getJson(query)
}
else if (type === 2) {
const rawData = await getRawData(query)
return rawToJson(rawData)
}
})
let resultJson = {}
for (const item of await Promise.all(items)) {
if (item === undefined) break;
Object.assign(resultJson, item)
}
return resultJson;
}
И да, я знаю что этот код не полностью эквивалентен исходному.
Эти два куска кода совсем не эквиваленты. Посыл был показать concurrent и что выполнить n-запросов один за другим – это далеко не тоже самое что выполнить n-запросов «сразу». И что это важно – понимать эту разницу. Но это никто в упор не хочет видеть.
проблема вашего и моего кода в другом – мы делаем лишние вызовы к getType.
>PS дихотомия между отверткой и молотком — ложная. Нужны оба инструмента!
Отличная мысль! давайте еще раз глянем в заглавие статьи. А потом посмотрим на примеры из нее – я не вижу там того что показал Shannon, но не смог сформулировать
А нужно было показать что в случаях с циклами с пост-условием(вот этот break) нам, таки, намного легче писать с async/await – и пофиг что это некрасиво, не-functional и вообще почти GOTO. И, наверное есть еще подобные примеры
В статье и комментариях я вижу как раз то о чем вы написали:
Promise.resolve([1, 2, 3]).map(getType)
сложнее чем(и впридачу, еквивалентно)
[1, 2, 3].map(query => {
const type = await getType(query);
return type
})
только в действительности первый вариант будет выполнятcя ~n времени(конечно, с оговорками), а второй 3*n времени. И если эту разницу игнорировать – то нам вообще не нужен async/await(как и promise), нам просто нужно синхронное IO
Если я что-то не понимаю – прошу поправить
И дело не в функциональщине vs императивщине, а в том, что оба подхода лучше сочетать, а не впадать в крайности.
По сути, обычно следующий запрос может зависит от ответа предыдущего, а иногда и сразу от двух предыдущих запросов, поэтому конкурентность не требуется. А когда она требуется, то просто делается все через Promise.all
Вот упрощенный пример из рабочего проекта:
const [userdata, params] = await Promise.all([getUserData(), getParams()])
let bricks = []
if (params.allowedExtraData && userdata.needExtraData) {
userdata.extraData = await getExtraData(userdata.id)
}
bricks.push(...params.bricksForModule[userdata.moduleType])
bricks.push(...params.bricksForType[params.mainType])
if (params.groups[userdata.groupType].isModerator) {
bricks.push(...patams.templatesModerator)
}
const bricksData = await Promise.all(bricks)
...
И дальнейшая обработка результатов
bricks.push(...patams.templatesModerator)
Не хотел вас поддеть или как-то обидеть
В следующий раз буду ставить много много смайликов – чтобы вы не подумали что я хочу вас обидеть
кусок кода с необъявленной переменной может скомпилироваться?
Это ведь JavaScript, конечно может. Или не JavaScript?
Это ведь JavaScript, конечно может. Или не JavaScript?Так и есть, мой промах, не учел что этот кусок кода в условии, которое может никогда не выполнится
Прикол в том, что даже если условие будет положительным вместо этой переменной подствиться undefined. А дальше уже соответствующие ошибки полезут, аля undefined is not a function
, cannot read property <...> of undefined
.
Единственное что, если без 'use strict' запускать, можно записать какое-нибудь значение в необъявленную переменную, она в глобальную область видимости попадет, но само по себе undefined не подставится
нет – со стороны выглядит будто вы этот кусок вырезали из кодаКак раз наоборот, код сократил раза в 3, а все переменные переименовал, чтобы легче смысл передать
с точкой зрения:
>PS дихотомия между отверткой и молотком — ложная. Нужны оба инструмента!
я вполне согласен – ваш первый пример, если его вдумчиво «покурить», показал что есть ситуация в которой async/await упрощает жизнь. Но в статье об этом ни слова.
Про ваш второй пример – я такого сказать не могу. Ну заменили вы .then на await. Вроде как стало на один уровень вложености меньше, но стоит вам добавить обработку исключений и все вернестя на свои места.
то что вам не нужно пробрасывать через .then пары аргументов – это плюс.
Ну и он страно написан – await Promise.all(bricks). возможно вы где-то кусок кода удалили – но, даже так, это странно складывать промисы и что-то «статитеское» в один и тот же список. Скорее кто-то на всякий случай обернул чтобы не думать что там внутри. Есть еще вариант, что там в params промисы где-то лежат – но это тоже странно.
>По сути, обычно…
бывает по разному. давайте все же смотреть на картину в целом.
>А когда она требуется, то просто делается все через Promise.all
Если вас не затруднит, проилюстрируйте свою идею кодом — по, возможности, перепишите код из этого комментария
https://habrahabr.ru/company/ruvds/blog/326074/#comment_10166298 , само сабой – с учетом опечатки и без break так чтобы делало как можно больше запросов «паралельно»
Ну и он страно написан – await Promise.all(bricks)bricks — это набор функции, которые возвращают промис.
Это модули, которые делают только свою маленькую работу, все они запускаются параллельно и их результат складывается в единый результат, а дальше в шаблонизаторе, уже в зависимости от наличия модуля будет активирован или нет нужный кусок шаблона
Ну заменили вы .then на await. Вроде как стало на один уровень вложености меньше, но стоит вам добавить обработку исключений и все вернестя на свои места.В данном случае, обработка исключений одна единственная на более высшем уровне (на точке входа), на каждом этапе она не требуется
>А когда она требуется, то просто делается все через Promise.allВот этот пример: https://habrahabr.ru/company/ruvds/blog/326074/#comment_10167050
Если вас не затруднит, проилюстрируйте свою идею кодом — по, возможности, перепишите код из этого комментария
Но если вам нужен именно пример моего изначального примера:
async function gogogo(queryList) {
let resultJson = {}
let promiseList = []
let rawDataPromiseList = []
for (const query of queryList) {
let json = {}
const type = await getType(query)
if (type === 1) {
promiseList.push(getJson(query))
}
else if (type === 2) {
rawDataPromiseList.push(getRawData(query))
}
else {
break
}
}
for (const rawData of await Promise.all(rawDataPromiseList)) {
promiseList.push(rawToJson(rawData))
}
for (const json of await Promise.all(promiseList)) {
Object.assign(resultJson, json)
}
return resultJson
}
И из цикла выйдем, и все запросы пойдут конкурентно, максимально насколько возможно в рамках данного примера. И даже больше, легко добавим более сложные условия:
const type = await getType(query)
const subtype = await getSubType(query)
if (type === 1 && subtype === 4) {
promiseList.push(getJson(query))
}
else if (type === 2 && !subtype) {
rawDataPromiseList.push(getRawData(query))
}
else if (subtype === 1) {
break
}
params.bricksForModule[userdata.moduleType] – тоесть это promise? или функция – если функция, то когда она вызывается?
За пример кода – спасибо. В принципе увидел, то что хотел.
из плюсов:
не меняет порядок эллеметов( у вас promiseList «как бы упорядочен»)
const Promise = require('bluebird')
function gogogo(queryList) {
const parts = []
for (const query of queryList) {
const type = await getType(query)
if (type === 1) {
parts.push(getJson(query))
}
else if (type === 2) {
parts.push(
getRawData(query).rawToJson(rawData)
)
} else {
break
}
}
return Promise
.all(parts)
.reduce(Object.assign, {})
}
В общем – спасибо за пример. Ну и всегда приятно пообщатся с человеком который подкрепляет свои суждения кодом. Приятных
только в действительности первый вариант будет выполнятcя ~n времени(конечно, с оговорками), а второй 3*n времени. И если эту разницу игнорировать – то нам вообще не нужен async/await(как и promise), нам просто нужно синхронное IO
Я ни коим образом не предлагаю эту разницу игнорировать! Напротив, я предлагаю совершенно другое: использовать async/await когда нужно последовательное выполнение и map + Promsie.all когда нужно параллельное.
Async/await: 6 причин забыть о промисах