node.js 12 异步 async

上一篇:


node.js


在我的node.js系列的开篇,有介绍过node.js具有单线程,异步,非阻塞的特点。如何理解这些特点?举一个例子,如果遇到耗时操作,比如网络交互或者磁盘IO,是不是需要等待操作结束再执行下一步操作?

对于node.js来讲,属于单线程,如果需要等待操作结束再执行后面的操作就麻烦了。

node.js选择的方式是在发起一个调用后不等待结果,继续向下执行。node.js这里采用的机制是异步+回调,通过异步和回调来实现对高并发的支持

回调函数

将一个函数作为参数传递给另一个函数,并且作为参数的函数可以被执行,其本质上是一个高阶函数。

我们用之前介绍过的文件模块中的函数举例。

例如,在执行读文件操作时,可以使用readFile方法。这个方法就是用了回调函数。

<code>fs.readFile('./test.txt', (err, data)=>{
if(err){
consolog.log(err)
return
}
console.log(data.toString())
})/<code>

嵌套回调

如果我们写代码需要读取三个文件,按照以前的方式,我们无法知道哪个文件的读取先结束。也就是说,如果我们需要先读取A文件,再读取B文件,只能进行回调函数的嵌套调用。

<code>//先读取A
fs.readFile('./A.txt', (err, data)=>{
if(err){
consolog.log(err)
return
}
//再读取B
fs.readFile('./A.txt', (err, data)=>{
}
})/<code>

如果需要读取多个文件并有明确的顺序要求,这个代码的可读性就很糟糕了。

在文件模块里,node.js还提供了另一个方法fs.readFileSync()。这是一个同步函数,可以直接得到结果。

但在业务逻辑中,面对大量的回调函数,如何进行操作呢?

使用Promise

Promise是对异步操作的封装

,提供了三个状态。

操作在执行中: Pending操作成功: Resolved操作失败: Rejected

从上面可以看出,pending是一个中间状态,一旦一个异步操作执行完成以后,或者转换为Resolved,或者转换为Rejected。

要使用Promise,首先需要用Promise的构造函数来封装一个现有的异步操作。我们以fs.readFile为例。

<code>function readFilePromise(path){
//初始化Promise
return new Promise(function(resolve, reject){
fs.readFile(path, (err, data)=>{
if(err){
reject(err)
}else{
resolve(data)
}
\t}
})
}/<code>

在回调函数中,需要将不同的返回结果传入resolve或者reject中。上例中,我们将error传入reject,表示读取文件出错,将data传入resolve,表示操作成功。

对于上面封装后的Promise,调用的时候可以通过then()来获取异步操作的值,即resolve的值。通过catch()方法来reject中的错误。

<code>promise
.then(function(data){
//成功
}).catch(function(data){
//出错
});/<code>

回到之前的问题,如果需要按顺序读取三个文件A,B,C。那么此时调用前面的promise,就可以promise

链式调用

<code>readFilePromise("./A.txt").then(function(data){
console.log(data);
return readFilePromise("./B.txt");
}).then(function(data){
console.log(data);
return readFilePromise("./C.txt");
}).then(function(data){
console.log(data);
})/<code>

看起来还是有点绕,有没有办法还是让代码实际异步执行,但程序看上去和同步一样呢?

async/await

这就是现在使用起来最为方便的async/await。程序在使用了async/await后看上去可以和同步的代码一样,可读性很强。

还是以刚才顺序读取三个文件为例。

<code>const fs = require('fs');
async function readData(fpath){
//顺序读取三个文件
let fa = await fs.readFile('./A.txt');
let fb = await fs.readFile('./A.txt');
let fc = await fs.readFile('./A.txt');
}
readData(fpath);/<code>

上面简洁的代码可以顺序读取三个文件A,B,C。最后只需要直接调用即可。

需要注意的是,在使用async/await时,函数前面一定要写async,在函数体中,对于异步函数的调用,一定要写await。在函数外调用该函数时,直接写函数名即可。

总结

从回调函数,到Promise,再到async/await,显示了node.js异步操作的不断演化。在async/await之前还有一个过度方案generator。目前实际使用中,async/await居多。

后面会介绍node.js体系里的另一个web框架Koa,和express不一样的地方其中就有异步的处理。Koa就是使用了async/await。

如果有什么问题,欢迎大家留言讨论。