2021/7/23
上次学习记录中,记录了异步API的回调函数,但往往我们在编程在回调函数中曾经出现一个现象,称之为“回调地狱”,我来一起来看看“回调地狱”是什么样子的
//需求: 能够依次读取文件1,2,3, 并依次拿到文件内容
const fs = require('fs');
fs.readFile('./1.txt', 'utf8', (err, result) => {
console.log(result);
fs.readFile('./2.txt', 'utf8', (err, result) => {
console.log(result);
fs.readFile('./3.txt', 'utf8', (err, result) => {
console.log(result);
})
})
})
对于上述的代码,太过于累赘,并且不利用代码维护。而Promise函数可以帮我们解决回调地狱的问题
一、Promise
Promise出现的目标是解决Node.js异步编程中回调地狱的问题(它实际上是一个构造函数,所以在使用之前需要定对它进行实例化)
const fs = require('fs');
function p1 () {
return new Promise((resolve, reject) => {
fs.readFile('./1.txt', 'utf8', (err, result) => {
/*resolve中的result中一个函数,调用了p1().then 中的回调函数,
并把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);
})
})
}
// then 中 return p2函数,对于链式编程中的后面的then来说就可以接收到p2的结果, p3同理
p1().then((r1) => {
console.log(r1);
return p2();
})
.then((r2) => {
console.log(r2);
return p3();
})
.then ((r3) => {
console.log(r3);
})
总结
- 其实对于回调地狱就是多重嵌套的问题,对于Promise解决方式就是在原本的回调函数外面嵌套一个Promise构造函数,可以利用构造函数的resolve参数把回调函数的结果引到外面,对于代码看起来也就更加的简洁,并且方便维护。
- 但编程人员又要在合适的时间和位置来调用不同的Promise函数,所以我又用了一个函数来包裹它(如:p1(), p2(), p3();)
p1().then((r1) => { console.log(r1); return p2(); })
在这里return p2(), 对于后面的.then((r2) => { console.log(r2); return p3(); })
中就可以拿到p2的返回结果,也加大了链式编程的方便性
二、Node.js异步函数
异步函数是异步编程语法的终极解决方案,它可以让我们将异步代码写成同步的形式,让代码不再有回调函数嵌套,全代码变得清晰明了。
const fn = async () = > {};
async function fn() {}
const fs = require('fs');
//1. 在普通函数定义的前面上加上async关键字,普通函数就变成了异步函数
//2. 异步函数默认的值是promise对象
//3. 在异步函数内部使用throw关键字进行错误的抛出
/*
await关键字
1.它只能出现在异步函数中
2. await promise它可以暂停异步函数的执行,等待promise对象返回结果后再向下执行函数
*/
async function p1 (callback) {
return 'p1';
}
async function p2 () {
return 'p2';
}
async function p3 () {
return 'p3';
}
//负责依次按顺序执行p1(), p2(), 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();
结果如下:
异步函数总结
async关键字
- 普通函数定义前加上async关键字,普通函数变异步函数
- 异步函数默认返回promise对象,省略了构造函数实例化过程(new Promise())
- 在异步函数内部使用return 关键字进行结果返回,结果会被包裹在promise对象中,return关键字代替了resolve方法
- 在异步函数内部使用throw关键字抛出程序异常;
- 调用异步函数再链式调用catch方法获取异步函数执行的错误信息
- 调用异步函数再链式调用then方法获取异步函数执行结果
p1().then(function (data) { console.log(data); })
await关键字
- await关键字只能出现在异步函数中
- await promise 后面只能写promise对象,写其他类型的API是不可以的
- await关键字可以暂停异步函数向下执行,直到promise返回结果(和异步对象的then方法同效果,依次执行)
精进版(三个文件依次读取内容)
const fs = require('fs');
//promisify改造现在异步函数API,让其返回promise对象,从而支持异步函数语法
const promisify = require('util').promisify;
//调用promiseify方法改造现有异步API,让其返回promise对象
const readFile = promisify(fs.readFile);
async function run() {
//使用经过改造的readFile方法可以返回promise对象,因此可以放在await后面
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();
程序思路讲解:
- 问题1: 因为 fs.readFlie() 方法不能返回promise对象,那么就代表着不能使用异步函数调用
回复1: 在util 模块中有一个promisify 方法,可以改造现有的异步API,让其返回 promise对象,从而支持异步函数语法(注: 在使用promisify方法之前要引用util 模块) - 问题2:
const promisify = require('util').promisify;
中,promisify中方法,为什么不是写成promisify()
回复2: promisify是模块util中的一个方法,这个方法在代码中没有加“()”,代表此时此刻不是在调用该方法,而是获取promisify方法,并把它赋值给promisify,抽取这个promisify() 方便使用。(同理:const readFile = promisify(fs.readFile);
中也是获取到readFile方法)