Node.js中的异步编程的初步了解


前言:这里只是列出node.js异步编程一些基本概念,对深入的原理和机制并不了解,只是对自己的学习知识的巩固。若有错误请指出。

异步API和同步API

同步API:只有当前API执行完毕后,才会继续向下执行。

异步API:当前异步API执行不会阻塞后续代码执行。

在node.js中常见的异步API:

fs readFile('./deom/txt',(err,result)=>{ //读取文件
	//operation
})
//服务器事件处理函数是异步API
const http = require("http");
const server = http.createServer();
server.on('request', (req, res) => { 
    //operation
})

异步API和同步API区别

1.返回结果不同

同步API可以直接从返回值中拿到相应结果,而异步API不可以。

var str = 'hello';
var str1 = str.concat(" node.js")
console.log(str1);

结果:
hello node.js

很显然这里的拼接字符串是同步API,可以直接获取返回值。

下面我们来看setTimeout()

function getMsg() {
    setTimeout(function () {
        return "hello node.js";
    }, 2000)
}
let msg = getMsg();
console.log(msg);

结果:

PS D:\> node .\dome.js
undefined

此处返回的结果并不是我们所想的hello node.js,这是因为setTimeout()是异步API,当函数执行到异步API时,会把异步API放到异步代码执行区内,然后继续执行同步API,当同步API执行完会再去执行异步API,当同步API执行完时就会隐式的return undefined,然后赋值给msg且输出,当再去异步API再去执行返回结果时,getMsg函数都已经返回undefined且输出了。

那么如何解决这个问题呢?我们来看下面这个代码:

function getMsg(callback) {
    setTimeout(function () {
        callback({
            msg: "hello node.js"
        });
    }, 2000);
}
getMsg(function (data) {
    console.log(data);
})

结果:

PS D:\> node .\dome.js
{ msg: 'hello node.js' }

这里和上面的代码相比加入了回调函数就可以返回异步API的结果,我们来分析一下过程,首先在getMsg函数中传入一个函数作为参数,即回调函数,当函数执行时会把回调函数传进setTimeout中,当setTimeout执行完后就会执行回调函数,在执行回调函数时传入一个对象作为参数。最后通过调用getMsg函数并传入回调函数得到setTimeout执行完返回结果。

所以异步API想要的到返回结果,可以通过回调函数,当异步API执行完毕后执行回调函数返回结果。

2.代码执行顺序不同

同步API:从上到下执行,上方代码会阻塞下方代码。

异步API:不用等待异步API执行完再去向下执行代码。可继续向下执行代码。

我们来看下面代码:

console.log("代码开始");
setTimeout(() => console.log("2s"), 2000);
setTimeout(() => console.log("0s"), 0);
console.log("代码结束了");

结果:

PS D:\> node .\dome.js
代码开始
代码结束
0s
2s

上面代码中,为什么在setTimeout中0秒的定时器却在“代码结束”后输出呢?

这里分析下执行过程:

同步代码执行区
异步代码执行区
回调函数队列

首先代码会从上到下依次执行,遇到“代码开始”放入同步代码执行区同时执行,然后遇到2s定时器放入异步代码执行区且不执行,同时会把异步API的回调函数放入回调函数队列且不执行。0s同上,最后把“代码结束”也放入同步代码执行区执行。现在同步全部执行,再去执行异步代码执行区,在当前异步代码执行完,就会把回调函数放入同步执行区执行。然后再去执行下一个异步API,重复。

所以可以看出,在代码执行时,异步API无论时间长短,即使0秒,都会等同步API执行完毕后再去执行异步API

promise

现在这里有这样一个问题:

当代码运行时候,一个异步API后面的代码依赖异步API返回的结果时该怎么办???

通常我们可以把一个依赖异步API代码放入异步API的回调函数,这样就可以在异步API结束后有返回值再去执行依赖部分。但是这样有很大的毛病。

有这样一个需求:有三个文件1.txt,2.txt,3.txt,要求依次读取文件。

const fs = require("fs");
fs.readFile('./1.txt', 'utf8', (err, result1) => {
    console.log(result1);
    fs.readFile('./2.txt', 'utf8', (err, result2) => {
        console.log(result2);
        fs.readFile('./3.txt', 'utf8', (err, result3) => {
            console.log(result3);
        });
    });
});

结果:

PS D:\新建文件夹> node .\dome.js
第一个文件
第二个文件
第三个文件

因为要求是依次读取,且fs.readFile()它是异步API,受文件大小影响fs.readFile()不能准确得到读取文件顺序。所以就要在读取完一个再去读取下一个,形成回调嵌套。

这样的代码晦涩难懂,难于维护,这样就形成的“回调地狱”。它是在异步API的回调函数中嵌套另外一个异步API,而另外一个异步API的回调函数再继续嵌套下一个异步API,往复循环。

解决方法:promise

  • Promise 是一个对象,它代表了一个异步操作的最终完成或者失败。
  • Promise是一个构造函数返回的对象。要通过new创建。

如何使用promise呢?

Promise是一个构造函数,通过new调用,模板:

let promise = new Promise((resolve, reject) => {//构造函数,创建promise对象
    setTimeout(() => {
        if (true) resolve("成功")//将异步API结果输出函数体外部
        else reject("失败信息")//若失败将失败信息输出函数体外部
    }, 2000);
});
promise.then(result => console.log(result))
    .catch(error => console.log(error))

在使用promise有以下约定:

  • 在本轮 事件循环 运行完成之前,回调函数是不会被调用的。
  • 即使异步操作已经完成(成功或失败),在这之后通过 then() 添加的回调函数也会被调用。
  • 通过多次调用 then() 可以添加多个回调函数,它们会按照插入顺序执行。

通过promise就可以解决“回调地狱”

const fs = require("fs");
const {
    resolve
} = require("path");
const {
    rejects
} = require("assert");
function p1() {
    return new Promise((resolve, rejects) => {
        fs.readFile('./1.txt', 'utf8', (err, result1) => {
            resolve(result1);
        })
    })
}
function p2() {
    return new Promise((resolve, rejects) => {
        fs.readFile('./2.txt', 'utf8', (err, result2) => {
            resolve(result2);
        })
    })
}
function p3() {
    return new Promise((resolve, rejects) => {
        fs.readFile('./3.txt', 'utf8', (err, result3) => {
            resolve(result3);
        })
    })
}
p1().then((result1) => {
    console.log(result1);
    return p2();
}).then((result2) => {
    console.log(result2);
    return p3();
}).then((result3) => {
    console.log(result3);
})

结果:

PS D:\新建文件夹> node .\dome.js
第一个文件
第二个文件
第三个文件

这里把promise放入新声明的函数中,是因为创建promise对象会立即执行,同时把声明promise对象返回方便后面链式调用。

这里明显比回调嵌套看起来干净整齐,同时更具有维护性。

异步函数

异步函数是ES2017的新特征。它可以将普通函数转化为异步函数,而且用return返回结果。(结果包裹在promise中,用return代替resolve)它的返回值是promise

const fs = require('fs');
const promisify = require('util').promisify;
const readFile = promisify(fs.readFile);
async function run() {
    let p1 = await readFile('1.txt', 'utf8');
    let p2 = await readFile('2.txt', 'utf8');
    let p3 = await readFile('3.txt', 'utf8');
    console.log(p1);
    console.log(p2);
    console.log(p3);
}
run();

结果:

PS D:\新建文件夹> node .\dome.js
第一个文件
第二个文件
第三个文件

这里首先获取util模块的promisify方法,这个方法会对异步API进行包装使其返回promise对象。只有promise对象才能使用异步操作。然后我们把fs.readFile方法传递进去就会得到一个新方法,能返回promise对象。

await关键字则是暂停异步API向下执行,(类似于同步执行)直到promise返回结果。注意:await后面只能是promise对象。

如有错误,请在底部留言。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值