Node.js中的异步编程

Node.js异步编程

一、同步API,异步API

Node.js当中有两种类型的API。一种是同步API,另一种是异步API。

什么是同步API: 就是只有当前API执行完成后才能继续执行下一个API。比如下面这个代码

console.log('before'); 
console.log('after');

在这里插入图片描述
可以看出的是程序会先在控制台当中输出before,然后再输出after。只有在第一个,执行完成之后才能执行第二个,这就是同步API。所谓同步就是指代码,从上到下一行一行的执行,只有上一行代码执行完成才能继续执行下一行代码。

什么是异步API: 异步API就是指当前API执行不会阻塞后续代码的执行。看一下下面这行代码

console.log('before');
setTimeout(
   () => { console.log('last');
}, 2000);
console.log('after');

上面的代码呢?可能有两种输出结果。这是第一种结果呢,就是先输出before两秒钟后输出last最后在输出after,第二种可能是程序先输出before在输出after最后输出last。那到底是哪一种呢,很简单运行一下就知道了。
在这里插入图片描述

通过运行程序,我们可以看到是第二种结果。在当前程序中,定时器就是一个异步API,程序不需要等待异步API执行完成之后再继续执行后面的代码,也就是说异步API不会阻塞后续代码的执行。在Node.js当中异步API几乎无处不在,所以掌握Node.js当中的异步编程非常重要。

异步
通常来说,程序都是顺序执行,同一时刻只会发生一件事。如果一个函数依赖于另一个函数的结果,它只能等待那个函数结束才能继续执行,从用户的角度来说,整个程序才算运行完毕.

在计算机普遍都有多核CPU的时代,坐在那里等待毫无意义,你完全可以在另一个处理器内核上干其他的工作,同时计算机完成耗时任务的时候通知你。这样你可以同时完成其他工作,这就是异步编程的出发点。你正在使用的编程环境(就web开发而言,编程环境就是web浏览器)负责为你提供异步运行此类任务的API。
产生阻塞的代码
异步技术非常有用,特别是在web编程。当浏览器里面的一个web应用进行密集运算还没有把控制权返回给浏览器的时候,整个浏览器就像冻僵了一样,这叫做阻塞;这时候浏览器无法继续处理用户的输入并执行其他任务,直到web应用交回处理器的控制。

二、同步API, 异步API的区别( 获取返回值 )

可以先看下面这个函数,这个函数的作用就是返回n1加n2的结果。

    // 同步
  function sum (n1, n2) { 
      return n1 + n2;
  } 
  const result = sum (10, 20);
  console.log(result);

在这里插入图片描述
在这里是在调用函数的时候。直接通过返回值的方式接收了函数的返回结果。

然后再来看另外一段代码。在这个函数里面,开启了一个定时器在两秒钟之后返回一个对象。对象的属性是msg,值是hello,然后在函数体的下面调用这个函数,同样还是通过返回值的方式去接收这个函数返回的结果,可以思考一下这个函数能不能拿到返回结果?

// 异步
  function getMsg () { 
      setTimeout(function () { 
          return { msg: 'Hello' }
      }, 2000);
      //return undefined;//默认
  }
  const msg = getMsg ();
  console.log(msg);

在这里插入图片描述
可以看到这个函数的输出结果是undefined。那为什么是undefined呢?在这里当我们去调这个函数的时候,由于里面的定时器是一个异步API,而异步API不会去阻止后续代码的执行,所以在函数的底部直接返回的就是一个undefined,因为这里没有写return,所以函数默认就是返回undefined。然后过了两秒钟以后,在这个函数内部返回了一个对象。但是,这个时候我们早就已经拿到了这个函数的返回值undefined,直接就进行了输出。所以这里就有一个结论: 就是在异步API里面,无法通过返回值的方式,去拿到异步API的返回值。

但是异步API的返回值我们到底应该怎样去拿到呢?实际上它是通过回调函数的方式去拿到的。什么是回调函数呢?

三、回调函数

回调函数简单说就是自己定义函数让别人去调用。

可以仔细看看这篇 深入理解:回调函数

下面我们来看一段代码,这段代码是回调函数其中的一种表现形式。

  // getData函数定义
 function getData (callback) {}
  // getData函数调用
 getData (() => {});

getData函数中,形参callback()对应的实参是一个匿名函数。

四、使用回调函数获取异步API执行结果

现在来改写上面异步函数使其输出返回值,以此來理解回调函数。

function getMsg (callback) { 
    setTimeout(function () { 
        callback({ msg: 'Hello Node.js' });
    }, 2000);
};
getMsg ((data)=>{
    console.log(data);
});

在这里插入图片描述

在上面代码中,callback()这个形参,它所对应的实参是一个函数,这个函数就是回调函数,因为原来的return起不到作用,所以在这里我们是调用callback()这个回调函数, 在调用这个函数之后,可以将这个异步API的结果,通过callback()函数参数的形式传递出去。也就是说调用callback()就相当于调用了回调函数,回调函数就是callback这个形参所对应的实参(匿名函数),然后可以在匿名函数中使用一个data参数去接收callback()中的参数,也就是异步API的执行结果。

五、同步API, 异步API的区别(代码执行顺序)

1、同步API从上到下依次执行,前面代码会阻塞后面代码的执行

for (var i = 0; i < 100; i++) { 
    console.log(i);
}
console.log('for循环后面的代码');

在这里插入图片描述

2、异步API不会等待API执行完成后再向下执行代码

console.log('代码开始执行'); 
setTimeout(() => { console.log('2秒后执行的代码')}, 2000);
setTimeout(() => { console.log('"0秒"后执行的代码')}, 0); 
console.log('代码结束执行');

在这里插入图片描述

六、代码执行顺序分析

console.log('代码开始执行');
setTimeout(() => {
    console.log('2秒后执行的代码');
}, 2000); 
setTimeout(() => {
    console.log('"0秒"后执行的代码');
}, 0);
console.log('代码结束执行');

在这里插入图片描述

在Node中代码从上到下执行代码的过程中,并不是遇到什么就执行什么,而是先将所有的同步API执行完,再去执行异步API。

在当前代码当中,由于console.log是同步代码,所以被放到了同步代码执行区,接下来遇到了两个定时器,由于定时器能是异步API,所以两个定时器被放到了异步代码执行区,紧接着同时Node会把异步API所对应的回调函数放到回调函数队列当中。这个时候要注意,不管是异步代码执行区还是回调函数队列,这个时候里面的代码是没有执行的。这里虽然有一个零秒的定时器,但是它首先还是一个异步API,所以Node首先还是会把它放到异步代码执行区当中把它对应的回调函数放到回调函数队列。

当同步代码执行完毕后,Node会再去异步代码执行区去执行异步代码,零秒的异步代码首先被执行,执行完异步代码后,Node就会到回掉函数队列里面去找到对应的回调函数,找到之后就会把它拿到同步代码执行区当中去执行得到执行结果。

七、Node.js中的异步API

Node.js中比较典型的的异步API:

 fs.readFile('./demo.txt', (err, result) => {});
 var server = http.createServer();
 server.on('request', (req, res) => {});

如果异步API后面代码的执行依赖当前异步API的执行结果,但实际上后续代码在执行的时候异步API还没有返回结果,这个问题要怎么解决呢?

fs.readFile('./demo.txt', (err, result) => {});
console.log('文件读取结果');

需求:依次读取A文件、B文件、C文件

回调地狱:

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)
		})
	})
});

八、Promise

Promise出现的目的是解决Node.js异步编程中回调地狱的问题。

let promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        if (true) {
            resolve({name: '张三'})
        }else {
            reject('失败了') 
        } 
    }, 2000);
});
promise.then(result => console.log(result); // {name: '张三'})
       .catch(error => console.log(error); // 失败了);
const fs = require('fs');

let promise = new Promise((resolve, reject) => {
	//fs.readFile('./1.txt', 'utf8', (err, result) => {
	fs.readFile('./100.txt', 'utf8', (err, result) => {

		if (err != null) {
			reject(err);
		}else {
			resolve(result);
		}

	});

});

promise.then((result) => {
	 console.log(result);
})
.catch((err)=> {
	console.log(err);
});
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)
// 		})
// 	})
// });

function p1 () {
	return new Promise ((resolve, reject) => {
		fs.readFile('./1.txt', 'utf8', (err, result) => {
			resolve(result)
		})
	});
}

function p2 () {
	return new Promise ((resolve, reject) => {
		fs.readFile('./2.txt', 'utf8', (err, result) => {
			resolve(result)
		})
	});
}

function p3 () {
	return new Promise ((resolve, reject) => {
		fs.readFile('./3.txt', 'utf8', (err, result) => {
			resolve(result)
		})
	});
}

p1().then((r1)=> {
	console.log(r1);
	return p2();
})
.then((r2)=> {
	console.log(r2);
	return p3();
})
.then((r3) => {
	console.log(r3)
});

九、异步函数

异步函数是异步编程语法的终极解决方案,它可以让我们将异步代码写成同步的形式,让代码不再有回调函数嵌套,使代码变得清晰明了。

const fn = async () => {};
async function fn () {}
1、async关键字
  1. 普通函数定义前加async关键字 普通函数变成异步函数
  2. 异步函数默认返回promise对象
  3. 在异步函数内部使用return关键字进行结果返回 结果会被包裹的promise对象中 return关键字代替了resolve方法
  4. 在异步函数内部使用throw关键字抛出程序异常
  5. 调用异步函数再链式调用then方法获取异步函数执行结果
  6. 调用异步函数再链式调用catch方法获取异步函数执行的错误信息
// 1.在普通函数定义的前面加上async关键字 普通函数就变成了异步函数
// 2.异步函数默认的返回值是promise对象
// 3.在异步函数内部使用throw关键字进行错误的抛出
// 
// await关键字
// 1.它只能出现在异步函数中
// 2.await promise 它可以暂停异步函数的执行 等待promise对象返回结果后再向下执行函数

// async function fn () {
// 	throw '发生了一些错误';
// 	return 123;
// }

// // console.log(fn ())
// fn ().then(function (data) {
// 	console.log(data);
// }).catch(function (err){
// 	console.log(err);
// })

async function p1 () {
	return 'p1';
}

async function p2 () {
	return 'p2';
}

async function p3 () {
	return 'p3';
}

async function run () {
	let r1 = await p1()
	let r2 = await p2()
	let r3 = await p3()
	console.log(r1)
	console.log(r2)
	console.log(r3)
}

run();
2、await关键字
  1. await关键字只能出现在异步函数中
  2. await promise await后面只能写promise对象 写其他类型的API是不不可以的
  3. await关键字可是暂停异步函数向下执行 直到promise返回结果
const fs = require('fs');
// 改造现有异步函数api 让其返回promise对象 从而支持异步函数语法
const promisify = require('util').promisify;
// 调用promisify方法改造现有异步API 让其返回promise对象
const readFile = promisify(fs.readFile);

async function run () {
	let r1 = await readFile('./1.txt', 'utf8')
	let r2 = await readFile('./2.txt', 'utf8')
	let r3 = await readFile('./3.txt', 'utf8')
	console.log(r1)
	console.log(r2)
	console.log(r3)
}

run();

看完不开心?想了解更多(来自MDN上相关内容):

发布了306 篇原创文章 · 获赞 263 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_44721831/article/details/104092590
今日推荐