Текущее поведение очень удивительно:
await async.parallel({foo: async()=>Promise.all([1,2])})
// { foo: [ 1, 2 ] }
await async.parallel({foo: ()=>Promise.all([1,2])})
// waits forever because non-async function expects a ballback
Потому что для коротких функций заманчиво просто написать foo: () => doSomething()
. Там очень легко забыть ключевое слово async
Но в настоящее время это не сработает, его нужно записать как foo: async () => doSomething()
Можем ли мы лучше проверить, передан ли обратный вызов или нет, вместо того, чтобы проверять, является ли функция AsyncFunction?
У нас нет возможности надежно определить, возвращает ли функция обещание или нет, и нужно ли нам заранее передать ей обратный вызов или нет. ( Function.length
или синтаксический анализ Function.toString()
недостаточно хорош) Это то, о чем asyncify
- преобразование функций, возвращающих обещание, в функции, которые принимают обратный вызов. Поскольку мы можем обнаруживать async
функции, поскольку они имеют другой тип, мы можем автоматически asyncify
их (как это работает внутри). Но для обычных функций, которые возвращают обещание, мы не можем сказать, прежде чем Async вызовет их.
@aearly хм, может быть, что-нибудь попроще, например, обнаружение обратного вызова на верхнем уровне?
async function parallel(o, cb) {
if (!cb) { // promise behavior
return Object.fromEntries(
await Promise.all(Object.entries(o).map(async ([k, v]) => [k, await v()]))
)
}
// else do the callback behavior
}
У нас уже так что-то вроде этого - вернуть обещание, если опустить обратный вызов на async.parallel
. Мы не предполагаем, что функции, которые вы передаете ему, возвращают обещания, если вы опускаете обратный вызов, потому что у нас нет возможности предположить намерение пользователя. Может я неправильно понимаю о чем ты?
Хорошо, давайте будем конкретнее: https://repl.it/@caub/async -async
// npm.im/async expects all inner functions to be async,
// even when using no outer callback (as 2nd arg of async.parallel)
console.log(
await async.parallel({
foo: async () => delay(1000).then(() => 1),
bar: async (rs) => {
return 2
},
})
);
// Doesn't work without those async,
// but I'd like this to resolve just like above instead of freezing
console.log(
await async.parallel({
foo: () => delay(1000).then(() => 1),
bar: () => {
return 2
},
})
);
Я думаю, что в будущей следующей основной версии было бы интересно поддерживать обратный вызов только таким образом:
await Promise.all([
{ then(resolve){ setTimeout(resolve, 50, 1) } }, // callback way
2,
delay(45).then(())=>3)
])
// [1, 2, 3]
устранение необходимости в ненужных ключевых словах async
во внутренних функциях
Преимущество в большей ясности. Я понимаю вашу точку зрения о том, что не предполагается намерение пользователя, но вы по-прежнему предполагаете, что люди не забудут ключевое слово async
когда захотят использовать разрешение Promise
Это в значительной степени нарушит обратную совместимость - например, вы больше не сможете делать такие вещи, как Async.map(fileNames, fs.readFile)
.
Не могли бы вы объяснить, где он сломается?
Что сейчас ломается:
await async.map(['server.js', 'package.json'], fs.readFile) // works
await async.map(['server.js', 'package.json'], fs.promises.readFile) // works
await async.map(['server.js', 'package.json'], s => fs.promises.readFile(s)) // doesn't work
await async.map(['server.js', 'package.json'], async s => fs.promises.readFile(s)) // works
По идее поправить 3-ю
Эти проблемы возникают у меня постоянно, например, я написал:
// ...
doc = await async.map(items, item => item.toDoc(currentUser, options));
где-то снова таймаут, потому что мне не хватало async item => item.toDoc..
Если я один в таком случае, это не стоит, но если в этой ситуации больше людей, я думаю, стоит рассмотреть предлагаемые изменения.
Этот пример с async.map
также очень репрезентативен, поскольку вы можете понять мою точку зрения о том, как
doc = await Promise.all(items.map(item => item.toDoc(currentUser, options)));
работает без async item => item.toDoc..
Надежно починить 3-ю невозможно, это уже много говорилось в прошлом. Мы не можем проверить возвращаемое значение функции, а затем задним числом не передать обратный вызов функции. Ключевое слово async
настолько хорошо, насколько возможно.
@caub Бесстыдный плагин, но я думаю, что asyncp
может справиться с тем, что вы здесь ищете.
Даже после того, как эта проблема закрыта, что касается связанной проблемы, указанной выше, я хотел бы еще раз подчеркнуть здесь утверждения @caub .
Текущее поведение меня сильно смутило, и хотя я (отчасти) понимаю обсуждаемые здесь технические ограничения, я считаю, что документация просто не соответствует текущему поведению.
Например, для mapLimit он говорит: «Возвращает: обещание, если обратный вызов не передан» - я начал с версии обратного вызова, а затем в основном исключил обратный вызов и был удивлен, что я не получаю обещание (потому что у меня не было ключевое слово async).
кроме того, тогда оператор callback в функции итератора не нужен, вместо этого вам нужно «вернуть» значение.
Итак, либо поведение должно быть изменено (предпочтительно), либо, по крайней мере, документация и примеры должны быть согласованы с моей точки зрения, чтобы избежать путаницы.
@caub Это причина
https://github.com/caolan/async/blob/8aecf108b3922bc5211036706a0f6f75e02bd42b/lib/internal/wrapAsync.js#L3 : L5
function isAsync(fn) {
return fn[Symbol.toStringTag] === 'AsyncFunction';
}
Таким образом, легче проверить, возвращает ли функция обещание, не вызывая его. Но все же согласен с вами, что это противоречит соглашениям async / await. Поскольку добавление ключевого слова async
без внутреннего await
на самом деле в большинстве случаев считается плохой практикой.
из https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
Тело функции
async
можно рассматривать как разделенное на ноль или более выражений ожидания. Код верхнего уровня, включая первое выражение ожидания (если оно есть), выполняется синхронно.
... и поэтому это будет бесполезно и, что хуже всего, добавит некоторые накладные расходы на производительность, если код, например, транслируется с помощью babel, поскольку он добавит больше кода и шагов только из-за наличия ключевого слова async
Например, следующее:
async function foo() { return 1 }
...эквивалентно:
function foo() { return Promise.resolve(1) }
но с этой библиотекой,
async.mapLimit([1], 1, async v => v)
и
async.mapLimit([1], 1, v => Promise.resolve(v)))
не эквивалентны!
Самый полезный комментарий
Даже после того, как эта проблема закрыта, что касается связанной проблемы, указанной выше, я хотел бы еще раз подчеркнуть здесь утверждения @caub .
Текущее поведение меня сильно смутило, и хотя я (отчасти) понимаю обсуждаемые здесь технические ограничения, я считаю, что документация просто не соответствует текущему поведению.
Например, для mapLimit он говорит: «Возвращает: обещание, если обратный вызов не передан» - я начал с версии обратного вызова, а затем в основном исключил обратный вызов и был удивлен, что я не получаю обещание (потому что у меня не было ключевое слово async).
кроме того, тогда оператор callback в функции итератора не нужен, вместо этого вам нужно «вернуть» значение.
Итак, либо поведение должно быть изменено (предпочтительно), либо, по крайней мере, документация и примеры должны быть согласованы с моей точки зрения, чтобы избежать путаницы.