Async: 将没有回调的函数视为承诺

创建于 2019-07-09  ·  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' 关键字)。
此外,此时不需要 iteratee 函数中的“回调”语句,而是需要“返回”一个值。
因此,无论是行为应该改变(首选)还是至少文档和示例应该与我的观点保持一致以避免混淆。

所有12条评论

我们无法可靠地检测函数是否返回承诺,以及我们是否需要事先将回调传递给它。 ( Function.length或解析Function.toString()还不够好)这就是asyncify作用——将返回 promise 的函数转换为接受回调的函数。 因为我们可以检测async函数,因为它们具有不同的类型,我们可以自动asyncify它们(这是内部工作的方式)。 但是对于碰巧返回promise的普通函数,在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关键字的需要

优点是更清晰。 我理解你关于不假设用户意图的观点,但你仍然假设人们在想要使用 Promise 解析时不会忘记async关键字

这会以相当大的方式破坏向后兼容性——例如,你不能再做像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

这个想法是修复第三个

我经常遇到这些问题,例如我写道:

  // ...
  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..

没有办法可靠地修复第三个,这在过去已经讨论了很多。 我们不能检查函数的返回值,然后追溯不向函数传递回调。 async关键字非常有用。

@caub无耻插件在这里,但我认为asyncp有能力处理你在这里寻找的东西。

即使这个问题已经关闭,对于上面链接的问题,我想在这里再次强调@caub声明。
当前的行为让我很困惑,虽然我(有点)理解这里讨论的技术限制,但我相信文档根本不符合当前的行为。
例如 mapLimit 它说“返回:一个承诺,如果没有通过回调” - 我从回调版本开始,然后基本上忽略了回调并且很惊讶我没有得到承诺(因为我没有'async' 关键字)。
此外,此时不需要 iteratee 函数中的“回调”语句,而是需要“返回”一个值。
因此,无论是行为应该改变(首选)还是至少文档和示例应该与我的观点保持一致以避免混淆。

@caub这就是原因
https://github.com/caolan/async/blob/8aecf108b3922bc5211036706a0f6f75e02bd42b/lib/internal/wrapAsync.js#L3 :L5

function isAsync(fn) {
    return fn[Symbol.toStringTag] === 'AsyncFunction';
}

这样,在不实际调用它的情况下,更容易检查函数是否返回了一个 promise。 但仍然同意你的看法,这违反了 async/await 约定。 在大多数情况下,添加没有内部awaitasync关键字实际上被认为是一种不好的做法。

来自https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

async函数的主体可以被认为是由零个或多个 await 表达式分割的。 顶级代码,直到并包括第一个 await 表达式(如果有),都是同步运行的。

...因此它将是无用的,最糟糕的是,如果代码由 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 等级