异步函数与等待的区别

3

我来自PHP背景,现在正在尝试学习NodeJS。

我知道Node中的所有内容都是异步的,但我发现我在我的代码中经常使用async/await组合,我想确保我没有做什么愚蠢的事情。

原因是我有很多情况需要在继续之前必须获取某些结果(例如一个小的ajax请求)。这个小的ajax请求除了按照我指定的顺序执行一系列操作外,没有其他目的。即使我按照“异步方式”编写并使用回调,我仍然必须等待正确的顺序完成所有操作。

现在每当我遇到这种情况时,我只需使用await等待结果:

例如:

var result = await this.doSomething(data);

与使用回调函数相反

this.doSomething(data, function(callback) {
  // code
  callback();
});

对我来说,第一个例子比第二个例子更简洁,这就是为什么我一直选择它的原因。但我担心我可能会错过一些基本的东西。但在情况下,异步调用下面没有其他处理,唯一的进展方式是遵循同步样式,那么使用第一个样式而不是第二个样式有什么问题吗?


2
我知道在Node中不是所有的东西都是异步的... - T.J. Crowder
第一种方法是较新的。第二种方法是较旧的。 - Daniel A. White
你是对的,第一种方法更简洁,但是你只能在异步函数内使用 await。请参考这里:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/await - arbuthnott
1个回答

6
我担心我可能会错过一些基本的东西。
不,你没有错,这正是你想要做的,假设this.doSomething(data)是异步的(如果它是一个ajax调用,希望它是异步的),并且它返回一个promise(所有使用async关键字定义的函数都会隐式返回promise)。 (如果它不是异步的,则不需要await,尽管允许这样做。)
您可能会因事情正在经历转变而感到有点困惑(可以理解)。 直到最近,在Node API中(内置和由第三方模块提供的API)中,压倒性的约定是使用“Node回调”模式,即执行异步工作的函数期望其最后一个参数是回调函数,该函数将使用第一个参数指示成功/失败(null =成功,任何其他值均为错误对象),随后的参数提供结果。 (您的第二个示例假设doSomething是其中之一,而不是返回promise的函数。)
示例:fs.readFile,您可以像这样使用它:
fs.readFile("/some/file", "utf-8", function(err, data) {
    if (err) {
        // ...handle the fact an error occurred..
        return;
    }
    // ...use the data...
});

这种编程风格很容易导致回调地狱,这也是承诺对象(又称“未来对象”)被发明的原因之一。
但是,很多 Node API 仍然使用旧的模式。
如果您需要使用“Node 回调”模式函数,则可以使用 util.promisify 创建启用承诺对象的版本。例如,假设您需要使用使用 Node 回调模式的 fs.readFile,则可以像这样获取承诺对象版本:
const readFilePromise = util.promisify(fs.readFile);

然后使用 async/await 语法(或直接通过 then 使用 Promise)来使用它:

const data = await readFilePromise("/some/file", "utf-8");

还有一个名为 promisifynpm 模块,可以提供整个 API 的 Promise 版本。(可能不止一个)

仅仅因为 Promises 和 async/await 在大多数情况下取代了旧的 Node 回调风格,并不意味着回调函数已经没有用处:Promise 只能被解决 一次。它们只适合一次性的事情。所以回调函数仍然有用处,例如在 EventEmitterson 方法中,如可读流:

fs.createReadStream("/some/file", "utf-8")
    .on("data", chunk => {
        // ...do something with the chunk of data...
    })
    .on("end", () => {
        // ...do something with the fact the end of the stream was reached...
    });

由于data会多次触发,因此使用回调函数是有意义的;Promise不适用。

另外请注意,您只能在async函数中使用await。因此,您可能会发现自己养成了一个类似于以下结构的“主”模块结构的习惯:

// ...Setup (`require` calls, `import` once it's supported, etc.)...
(async () => {
    // Code that can use `await `here...
})().catch(err => {
    // Handle the fact an error/promise rejection occurred in the top level of your code
});

你可以不使用catch,这样当出现未处理的错误/拒绝时,你的脚本就会终止。(Node目前还没有这样做,但一旦未处理的拒绝检测成熟,它就会这样做。)
或者,如果你想在非async函数中使用一个启用了promise的函数,只需使用thencatch
this.doSomething()
    .then(result => {
        // Use result
    })
    .catch(err => {
        // Handle error
    });

注意:在 Node 7.x 及以上版本中,直接支持使用 async/await。如果要使用 async/await,请确保目标生产环境支持 Node 7.x 或更高版本。否则,您可以使用类似 Babel 的工具对代码进行转译,然后在旧版本的 Node 上使用转译后的结果。

2
承诺适用于仅发生一次的事件。回调应该用于那些发生多次的事件。 - Joshua
@Crowes:非常好的观察,我会想办法加入其中。大多数旧式Node API函数在一次性操作中使用回调(当然除了EventEmitters),但值得注意的是,回调仍然有其作用。 - T.J. Crowder

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接