Async: Рассматривайте функции без обратного вызова как обещания

Созданный на 9 июл. 2019  ·  12Комментарии  ·  Источник: caolan/async

Текущее поведение очень удивительно:

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?

wont fix

Самый полезный комментарий

Даже после того, как эта проблема закрыта, что касается связанной проблемы, указанной выше, я хотел бы еще раз подчеркнуть здесь утверждения @caub .
Текущее поведение меня сильно смутило, и хотя я (отчасти) понимаю обсуждаемые здесь технические ограничения, я считаю, что документация просто не соответствует текущему поведению.
Например, для mapLimit он говорит: «Возвращает: обещание, если обратный вызов не передан» - я начал с версии обратного вызова, а затем в основном исключил обратный вызов и был удивлен, что я не получаю обещание (потому что у меня не было ключевое слово async).
кроме того, тогда оператор callback в функции итератора не нужен, вместо этого вам нужно «вернуть» значение.
Итак, либо поведение должно быть изменено (предпочтительно), либо, по крайней мере, документация и примеры должны быть согласованы с моей точки зрения, чтобы избежать путаницы.

Все 12 Комментарий

У нас нет возможности надежно определить, возвращает ли функция обещание или нет, и нужно ли нам заранее передать ей обратный вызов или нет. ( 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)))

не эквивалентны!

Была ли эта страница полезной?
0 / 5 - 0 рейтинги