我们知道,如果我们以同步的方式编写耗时的代码,那么就会阻塞JS的单线程,造成CPU一直等待IO完成才去执行后面的代码;而CPU的执行速度是远远大于硬盘IO速度的,这样等待只会造成资源的浪费。异步IO就是为了解决这个问题的,异步能尽可能不让CPU闲着,它不会在那等着IO完成;而是传递给底层的事件循环一个函数,自己去执行下面的代码。等磁盘IO完成后,函数就会被执行来作为通知。
虽然异步和回调的编程方式能充分利用CPU,但是当代码逻辑变的越来越复杂后,新的问题出现了。请尝试用异步的方式编写以下逻辑代码:
先判断一个文件是文件还是目录,如果是目录就读取这个目录下的文件,找出结尾是txt的文件,然后获取它的文件大小。
恭喜你,当你完成上面的任务时,你已经进入了终极关卡:Callback hell回调地域!
就是每一个逻辑都要写一个回调, 导致回调地狱
为了解决Callback hell的问题,Promise
和async/await
诞生。
-
promise
的作用是对异步回调代码包装一下,把原来的一个回调函数拆成2个回调函数,这样的好处是可读性更好。语法如下:语法注意:Promise内部的resolve和reject方法只能调用一次,调用了这个就不能再调用了那个;如果调用,则无效。
// 一段逻辑:
// 先判断一个文件是文件还是目录,如果是目录就读取这个目录下的文件列表,
// 找出结尾是txt的文件,然后获取它的文件大小。
let fs = require("fs");
let path = require('path');
let util = require('util');
function withoutPromise() {
let target = "test";
fs.stat(target, (err, stat)=>{
if(err){
throw err;
}
// 如果是文件夹
if(stat.isDirectory()){
fs.readdir(target, (err, files)=>{
// 遍历files
files.forEach( f =>{
if(path.extname(f) === '.txt'){
fs.stat(path.join(target, f), (err, stat)=>{
console.log(f+ " : "+stat.size);
});
}
} );
});
}
});
}
// 用promise和async/await的风格来改下上面的逻辑
async function withPromise() {
let target = "test";
//将fs.stat转为一个可以返回Promise对象的方法
let pstat = util.promisify(fs.stat);
let stat = await pstat(target);
// 如果是文件夹
if(stat.isDirectory()){
//将fs.readdir转为一个可以返回Promise对象的方法
let preaddir = util.promisify(fs.readdir)
//文件夹得到了一个数据保存到files里
let files = await preaddir(target)
files.forEach( async (f) => {
if(path.extname(f) === '.txt'){
let stat = await pstat(path.join(target, f));
console.log(stat.size);
// fs.stat(path.join(target, f), (err, stat)=>{
// console.log(f+ " : "+stat.size);
// });
}
});
}
}
withPromise();
async/await
的作用是直接将Promise异步代码变为同步的写法,注意,代码仍然是异步的。这项革新,具有革命性的意义。
语法要求:
-
await
只能用在async
修饰的方法中,但是有async
不要求一定有await
。 -
await
后面只能跟async
方法和promise
。 -
实际上很多的库都以及封装好了promise库, 直接await就可以了
假设拥有了一个promise对象,现在使用async/await可以这样写:
async function asyncDemo() {
try {
// 当promise的then方法执行的时候
let text = await promise //重点: await的底层是异步执行的
// 当你用promise包装了所有的异步回调代码后,就可以一直await,真正意义实现了以同步的方式写异步代码
console.log('异步道明执行');
}catch (e){
// 捕获到promise的catch方法的异常
console.log(e);
}
}
asyncDemo()
console.log('我是同步代码');
使用nodejs中文网里的SDK里的util工具类, util.promisify这个方法可以方便的把所有的异步回调都转成promise, 这样就可以
通过promise把所有的异步操作包装起来, 用await语法来调用promise, 用try/catch捕获promise内部的异常
异步代码的终极写法:
-
先使用
promise
包装异步回调代码,可使用node提供的util.promisify
方法; -
使用
async/await
编写异步代码。 -
简单点说就是promise包装, async来调用