回调函数因为涉及的内容多而杂,并且在项目中也不怎么使用,所以在这里就先不说了,

本章重点讲解一下 Promise、generator + co、async/await

因为里面内容会有点多,并且还有好多代码示例。所以需要静下心慢慢看,相信看完之后,你肯定会对这三种方法涉及的异步问题的理解更上一层楼

如果想要大致了解一下的话,可以看看我的这篇文章《JS中异步处理方案》

咱们先说 Promise,然后慢慢涉及到其他,循序渐进(其实这是JS处理异步的一个发展流程)

开始吧!!!

Promise


Promise 简单的说就是一个容器,里面保存着某个未来才会结束的时间(通常是一个异步操作)的结果。从语法上说,Promise 就是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法处理。

如何理解:

  • 没有异步就不需要 promise
  • promise 本身不是异步,只是我们去编写异步代码的一种方式

promise 有所谓的 4 3 2 1


4 大术语
一定要结合异步操作来理解
既然是异步,这个操作需要有个等待的过程,从操作开始,到获取结果,有一个过程的

  • 解决(fulfill)指一个 promise 成功时进行的一系列操作,如状态的改变、回调的执行。虽然规范中用 fulfill 来表示解决,但在后世的 promise 实现多以 resolve 来指代之
  • 拒绝(reject)指一个 promise 失败时进行的一系列操作
  • 终值(eventual value)所谓终值,指的是 promise 被解决时传递给解决回调的值,由于 promise 有一次性的特征,因此当这个值被传递时,标志着 promise 等待态的结束,故称之终值,有时也直接简称为值(value)
  • 据因(reason)也就是拒绝原因,指在 promise 被拒绝时传递给拒绝回调的值

3 种状态
在异步操作中,当操作发出时,需要处于等待状态
当操作完成时,就有相应的结果,结果有两种:

  • 成功了
  • 失败了

一共是 3 种状态,如下:

  • 等待态(Pending (也叫进行态)
  • 执行态(Fulfilled)(也叫成功态)
  • 拒绝态(Rejected) (也叫失败态)

2024/08/22/1724302786447.webp

针对每一种状态,有一些规范:

等待态(Pending)
处于等待态时,promise 需满足以下条件:

  • 可以迁移至执行态或拒绝态

执行态(Fulfilled)
处于执行态时,promise 需满足以下条件:

  • 不能迁移至其他任何状态
  • 必须拥有一个不可变的终值

拒绝态(Rejected)
处于拒绝态时,promise 需满足以下条件:

  • 不能迁移至其他任何状态
  • 必须拥有一个不可变的据因

2 种事件
针对 3 种状态,只有如下两种转换方向:

  • pending → fulfilled
  • pendeing → rejected

在状态转换的时候,就会触发事件:

  • 如果是pending → fulfiied,就会触发 onFulFilled 事件
  • 如果是pendeing → rejected,就会触发 onRejected 事件

在调用 resolve 方法或者 reject 方法的时候,就一定会触发事件
需要注册 onFulFilled 事件 和 onRejected 事件
针对事件的注册,Promise 对象提供了 then 方法,如下:
promise.then(onFulFilled,onRejected)

针对 onFulFilled,会自动提供一个参数,作为终值(value)
针对 onRejected,会自动提供一个参数,作为据因(reason)

1 个对象

promise

注:只有异步操作的结果,可以决定当前是哪一种状态,任何其他的操作都无法改变这个状态

基本使用


当我们创建 promise 时,会默认的处于 Pending 状态,并且在创建的时候,promise 中一定要有一个执行器,并且这个执行器会立即执行

// ()=>{} 叫执行器,会立即执行
let p = new Promise(()=>{ })
// 刚创建的 Promise 默认处理 Pending 状态
console.log(p); // Promise { <pending> }

在 promise 的执行器中需要传入两个参数,分别是 resolve 和 reject ,在内部调用时,就分别代表状态由 pending=>fulfilled(成功) pending=>rejected(失败)

并且一旦 promise 状态发生变化之后,之后状态就不会再变了。比如:调用 resolve 之后,状态就变为 fulfilled,之后再调用 reject,状态也不会变化

let p = new Promise((resolve,reject)=>{
    resolve("有钱了....")  // 现在 promise 就处理成功态
})
console.log(p); // Promise { '有钱了....' }
// 失败态就不演示了

切记状态发生变化之后,之后状态就不会再变了

// 一个 promise 的状态只能从等待到成功,或从等待到失败
let p = new Promise((resolve,reject)=>{
    resolve("有钱了...")  // 成功了....
    reject("没钱了...")  // 失败了....
})
p.then(()=>{
    console.log("成功了....")
},()=>{
    console.log("失败了...")
})
// 只能输出  成功了...

then 方法


上面代码已经看到了,在使用时可以通过 promise 对象的内置方法 then 进行调用,then 有两个函数参数,分别表示 promise 对象中调用 resolve 和 reject 时执行的函数

let p = new Promise((resolve,reject)=>{
    // resolve("有钱了...")  // 成功了....
    reject("没钱了...")  //失败了....
})
// 在 then 方法中,有两个参数
// 第一个参数表示从等待状到成功态,会调用第1个参数
// 第二个参数表示从等待状到失败态,会调用第2个参数
p.then(()=>{
    console.log("有钱了....")
},()=>{
    console.log("没钱了...")
})
// 输出结果 没钱了...

在执行完后,成功肯定有一个成功的结果 失败肯定有一个失败的原因,那么如何得到成功的结果 ? 如何得到失败原因呢?

let p = new Promise((resolve,reject)=>{
    // 调用 reolve 时,可以把成功的结果传递下去
    // resolve("有钱了...")  // 成功了...
    // 调用 reject 时,可以把失败的原因传递下去
    reject("没钱了...")  // 失败了....
})
p.then((suc)=>{
    console.log(suc)
},(err)=>{
    console.log(err)
})
// 输出结果  没钱了...

当我们在执行失败处理时,也可以用 throw,就是抛出一个错误对象,也是失败的

如下:

let p = new Promise((resolve,reject)=>{
    // throw 一个错误对象  也是失败的
    throw new Error("没钱了...")
})
p.then((suc)=>{
    console.log(suc);
},(err)=>{
    console.log(err);
})

throw 的定义:throw 语句用来抛出一个用户自定义的异常。当前函数的执行将被停止(throw 之后的语句将不会执行),并且控制将被传递到调用堆栈中的第一个 catch 块。如果调用者函数中没有 catch 块,程序将会终止。

// 尝试一下
function getRectArea(width, height) {
  if (isNaN(width) || isNaN(height)) {
    throw "Parameter is not a number!";
  }
}
try {
  getRectArea(3, 'A');
}
catch(e) {
  console.log(e);
  // expected output: "Parameter is not a number!"
}

promise 本身是同步的

// promise 本身是同步的
console.log("start")
let p = new Promise(()=>{
    console.log("哈哈")  // 哈哈
})
console.log("end")  
// 输出顺序   start  哈哈   end

并且在执行器的内部也是可以写异步代码的

那么 then 中的方法什么时候调用呢?
只有当调用 resolve,reject 时才会去执行 then 中的方法

let p = new Promise((resolve,reject)=>{
    setTimeout(()=>{
        console.log("setTimeout")
        // resolve("有钱了...")
        reject("没钱了...")
    },1000)
})
p.then((suc)=>{
    console.log(suc)  // 有钱了...
},(err)=>{
    console.log(err)  // 没钱了...
})

链式调用(重点)


先我们在目录下面建两个文件,分别是:name.txt 和 age.txt
name.txt 文件里写了一个 age.txt
age.txt 文件里写了一个 666

下面就以读取文件为例,来演示链式调用(需要了解一点 node 基础)

当你读取文件的时候,如果你用的是 vscode 编辑器,里面会有一个小 bug,用相对路径可能会出错,所以最好使用绝对路径

读取文件:

let fs = require("fs")
let path = require("path")
let filename = path.join(__dirname,"name.txt")
fs.readFile(filename,"utf8",(err,data)=>{
    if(err){
        console.log(err)
    }
    fs.readFile(path.join(__dirname,data),"utf8",(err,data)=>{
        if(err){
            console.log(err)
        }
        console.log(data)
    })
})
// 输出结果 666

如果用这种方法,就会出现 回调地狱 ,很难受,所以一般不用

在读取文件时,我们可以专门 封装一个函数 ,功能就是读取一个文件的内容

let fs = require("fs")
// 封装一个函数,函数的功能是读取一个文件的内容
// rest 参数(下去自己了解一下,就是可以获取到传过来的所有内容)  
function readFile(...args){
    return new Promise((resolve,reject)=>{
        fs.readFile(...args,function(err,data){
            if(err) reject(err)
            resolve(data)
        })
    });
}
// 读文件
readFile("./name.txt","utf8").then(data=>{
    console.log(data)   
},err=>{
    console.log(err)  
})
// 输出结果  age.txt

如果文件不存在,会走第二个函数

let fs = require("fs")
function readFile(...args){
    return new Promise((resolve,reject)=>{
        fs.readFile(...args,function(err,data){
            if(err) reject(err)
            resolve(data)
        })
    });
}
// 如果 name1 不存在,走 then 的第 2 个函数
readFile("./name1.txt","utf8").then(data=>{
    console.log(data)  
},err=>{
    console.log(err)   
})
// 报错  no such file or directory

那么如果我们想要读取 age.txt 里面的内容呢?
我们可以这么写:

let fs = require("fs")
function readFile(...args){
    return new Promise((resolve,reject)=>{
        fs.readFile(...args,function(err,data){
            if(err) reject(err)
            resolve(data)
        })
    });
}
 
readFile("./name.txt","utf8").then(data=>{
    // console.log(data)  // age.txt
    readFile(data,"utf8").then(data=>{
        console.log(data)  // 666
    },err=>{
        console.log(err)  
    })
},err=>{
    console.log(err)  
})
// 输出结果  666

这样写就可以获取到 age.txt 文件里面的内容,但是呢,这样写又回到了 回调地狱 ,不是说这种方法不行,而是不够优雅

使用 链式调用
在 promise 中可以链式调用 就是 .then 之后,还可以 .then ,你可以无数次的 .then
.then 之后又返回了一个新的 promise,就是 .then 的函数参数中会默认返回 promise 对象,所以当你碰到 .then 连续调用的时候,你就可以把前面的所有代码当成一个 promise

let fs = require("fs")
function readFile(...args){
    return new Promise((resolve,reject)=>{
        fs.readFile(...args,function(err,data){
            if(err) reject(err)
            resolve(data)
        })
    });
}

readFile("./name.txt","utf8").then(data=>{
    // console.log(data)  // age.txt
    return false;
},err=>{
    console.log(err)  
}).then(data=>{  // 这里面的 data 是上一个 then 中的第一个函数的返回值,这个 .then 前面的一坨代码就可以当成一个promise
    console.log(data)  // false
},err=>{

})

如果没有这个文件,则返回错误信息

let fs = require("fs")
function readFile(...args){
    return new Promise((resolve,reject)=>{
        fs.readFile(...args,function(err,data){
            if(err) reject(err)
            resolve(data)
        })
    });
}
readFile("./name1.txt","utf8").then(data=>{
    return data;
},err=>{
    return err;
    // console.log(err)  
}).then(data=>{  
    console.log(data)
},err=>{
    console.log(err)
})
// 输出结果   no such file or directory

但是如果我们返回一个 promise 呢?
那么这个 promise 会执行,并且会采用他的状态

let fs = require("fs")
function readFile(...args){
    return new Promise((resolve,reject)=>{
        fs.readFile(...args,function(err,data){
            if(err) reject(err)
            resolve(data)
        })
    });
}

readFile("./name.txt","utf8").then(data=>{
    return data;
},err=>{
    return err;
    // console.log(err)  
}).then(data=>{  
    // console.log(data)
    return new Promise((resolve,reject)=>{	//返回一个promise
        reject("不OK")	//下面的.then采用这个状态(失败态)
    })
},err=>{}).then(data=>{
    console.log(data)
},err=>{
    console.log(err)  // 不OK
})
// 输出结果  不OK

所以如果返回的是一个 promise,那么这个 promise 会执行,并且会采用它的状态

小总结:

如果在上一个 then 的第一个函数中,返回一个普通值,那么无论你是在第 1 个函数中返回,还是在第 2 个函数中返回,都会作为下一个 then 的成功的结果,如果不返回,undefined 就作为下一个 then 的成功的结果

如果返回的是一个 promise,会作为下一个 then 的 promise 对象,data err 去 promise 对象中去取,也就是说,前一个 then 的返回值,会作为后一个 then 的参数

再给两个小例子,自己看一下:

let p = new Promise((resolve,reject)=>{
    setTimeout(()=>{
        resolve("hello")
    },1000)
})
p.then(data=>{
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            resolve("world")
        },1000)
    })
}).then(data=>{
    console.log(data)  
},err=>{

})
// 输出结果 world
let p = new Promise((resolve,reject)=>{
    resolve("hello")
})
let p1 = p.then(data=>{
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            reject("不OK")
        },1000)
    })
})
p1.then(data=>{
    console.log(data)  
},err=>{
    console.log(err) 
})
// 输出结果   不OK

一个坑(循环引用)

接下来说一个小问题,链式调用中的 循环引用

有的人不喜欢把前面的一大堆代码后面加 .then,所以就用了下面的一种写法(有可能会出现 循环引用):

let p = new Promise((resolve,reject)=>{
    resolve("hello")
})

let p1 = p.then(data=>{
    return p1; // 循环引用 报错 p1 在等 p1 的状态改变,也就是我在等我吃饭,显然是不行的
})

p1.then(data=>{
    console.log(data)
},err=>{
    console.log("-----",err); // 可执行,然后报错
})

如果我们把状态确定住,那就可以了

let p = new Promise((resolve,reject)=>{
    resolve("hello")
})
let p1 = p.then(data=>{
    // return 123  相当于把等待态改变成成功态
    return 123
})
p1.then(data=>{
    console.log(data); // 123
},err=>{
    console.log("-----",err);
})
// 输出 123

当然改变成失败态也可以

let p = new Promise((resolve,reject)=>{
    resolve("hello")
})
let p1 = p.then(data=>{
    // return new Error("不OK") 把等待态变成失败态
    return new Error("不OK")
})
p1.then(data=>{
    console.log(data) 
},err=>{
    console.log(err); // Error: 不OK
})

递归解析


看一个问题

let p = new Promise((resolve,reject)=>{
    resolve("hello")
})
let p1 = p.then(data=>{
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            resolve(new Promise((resolve,reject)=>{
                setTimeout(()=>{
                    resolve("666")
                },1000)
            }))
        },1000)
    })
})
// data 是 promise 还是 666
p1.then(data=>{
    console.log(data)  
},err=>{
    console.log(err) 
})

按理说,data 打印出来的是一个 promise,就是上面 resolve 里面的一堆代码
然而并不是,promise 会进行递归解析,到最后上面代码会打印出来 666

如果在 resolve 或 reject 中又是一个 promise,那么就会递归解析(无论有多少个 promise)

let p = new Promise((resolve, reject) => {
    resolve("hello")
})
let p1 = p.then(data => {
    return new Promise((resolve, reject) => {
        resolve(new Promise((resolve, reject) => {
            resolve(new Promise((resolve, reject) => {
                resolve(new Promise((resolve, reject) => {
                    resolve(new Promise((resolve, reject) => {
                        resolve(new Promise((resolve, reject) => {
                            resolve("666")
                        }))
                    }))
                }))
            }))
        }))
    })
})
p1.then(data => {
    console.log(data)
}, err => {
    console.log(err)
})
// 打印结果  666

catch 方法


  • catch 方法,用于注册 onRejected 回调
  • catch 其实是 then 的简写,then(null,callback)

catch 就是 .then 的语法糖

如果 .then 中有第 2 个函数,在这个 .then 后面又有 catch,如果到失败态,那么会走 .then 的第 2 个函数

let p = new Promise((resolve,reject)=>{
    reject("不OK")
})

p.then(data=>{

},err=>{
    console.log("1",err)
}).catch(err=>{
    console.log("2",err)
})
// 输出结果 1 不OK

如果 .then 中没有第 2 个函数,在这个 .then 后面又有 catch,如果到失败态,那么会走 catch

let p = new Promise((resolve,reject)=>{
    reject("不OK")
})
p.then(data=>{

}).catch(err=>{
    console.log("2",err)
})
// 输出结果  2 不OK

一个坑

在 .then 第二个函数中,return err 它是 return 到了下一个.then 的第一个函数中

let p = new Promise((resolve,reject)=>{
    reject("不OK")
})
p.then(data=>{

},err=>{
    // 在这里它并没有 reutrn 到 err 中,它 reutrn 到第一个参数中
    return err
}).then(data=>{
    console.log("data----",data)
},err=>{
    console.log("err----",err)
})
// 输出结果 data---- 不OK

所以最终:
一个 promise 中,一般在 then 中只有一个函数,在 then 后面有一个 catch,一般使用 then 来获取 data,在 catch 中获取 err

let p = new Promise((resolve,reject)=>{
    resolve("OK")
})

p.then(data=>{
    console.log(data)
}).catch(err=>{
    console.log(err)
})

静态方法


在 Pomise 类上面,提供了几个静态方法:

  • resolve
  • reject
  • finally
  • all
  • race
  • resolve

resolve

Promise.resolve("有钱了...").then(data=>{
     console.log(data)  // 有钱了...
})

等价于下面这种写法:

let p = new Promise((resolve,reject)=>{
    resolve("有钱了...")
})
p.then(data=>{
    console.log(data)
})

reject

Promise.reject("没钱了...").catch(data=>{
    console.log(data)  // 没钱了...
})

finally

不管转成成功态还是失败态,都会调用finally这个方法

Promise.resolve("有钱").then(data=>{
    console.log(data)
}).finally(()=>{
    console.log("开心...")
})
// 打印结果  有钱   开心...
Promise.reject("没钱").catch(data=>{
    console.log(data)
}).finally(()=>{
    console.log("不开心...")
})
// 打印结果  没钱   不开心...

all

all 表示 [ ] 中的 promise 都成功了,才能得到最终的值

注意里面是一个数组
读取 name.txt 和 age.txt 中的内容

let fs = require("fs").promises;
// all 表示 [] 中的 promise 都成功了,才能得到最终的值
Promise.all([fs.readFile("./name.txt","utf-8"),fs.readFile("./age.txt","utf-8")]).then(data=>{
    console.log(data) // [ 'age.txt', '666' ]
})
// 打印结果  [ 'age.txt', '666' ]

如果有一个不成功,那么就不行

let fs = require("fs").promises;
Promise.all([fs.readFile("./name1.txt","utf-8"),fs.readFile("./age.txt","utf-8")]).then(data=>{
    console.log(data)
})
// 这个是不行的

race

顾名思义,race 就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3]) 里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。

let fs = require("fs").promises;
Promise.race([fs.readFile("./name.txt","utf-8"),fs.readFile("./age.txt","utf-8")]).then(data=>{
    console.log(data) // age.txt
})

改变文件里面的内容,多尝试几次

generator + co


我很佩服你能看到这里,厉害

先说 生成器 和 迭代器

生成器可以生成迭代器,可以让程序中断,不会把 { } 中的代码全部执行

用法:在 function 和自己声名的名称之间加一个 * 号,里面用 yield
产出数据,然后调用生成器生成迭代器

function * read(){
    yield 1;  // 只有产出,并不执行
}
// 调用生成器 生成 迭代器   it 就是迭代器  
let it = read();

生成器可以产出很多值,迭代器只能 next 一下,拿一个值,next 一下,拿一个值

function * read(){
    yield 1;  
}
let it = read()
console.log(it.next()); // { value: 1, done: false }
console.log(it.next()); // { value: undefined, done: true }
function * read(){
    yield 1;  
    yield 2;
    yield 3;
}
// 调用 read()  返回值是迭代器
let it = read()
console.log(it.next());  // { value: 1, done: false }
console.log(it.next());  // { value: 2, done: false }
console.log(it.next());  // { value: 3, done: false }
console.log(it.next());  // { value: undefined, done: true }

如果 next 中有参数的话,那么他会把这个参数传给上一个生成器声明的变量里

所以第一个 next 中的参数没有任何意义,我们一般不写

function * read(){
    let a = yield 1; 
    console.log(a)    // 9
    let b = yield 2;
    console.log(b)    // 10
    let c = yield 3;
    console.log(c)   // 11
}
let it = read()
console.log(it.next())   // { value: 1, done: false }
console.log(it.next(9))    // { value: 2, done: false }
console.log(it.next(10))   // { value: 3, done: false }
console.log(it.next(11))   // { value: undefined, done: true }

接下来用这个实现我们的读文件操作,哈哈,是不是很恶心

读取 name.txt 文件

const fs = require("fs").promises;
// 生成器
function * read(){
    yield fs.readFile("./name.txt","utf-8");
}
// 迭代器
let it = read();
// console.log(it.next()); // { value: Promise { <pending> }, done: false }
it.next().value.then(data=>{ // 因为是一个对象,所以直接 .value
    console.log(data);
})
// 输出结果 age.txt

然后读取 age.txt 文件

const fs = require("fs").promises;
function * read(){
    let concent = yield fs.readFile("./name.txt","utf-8")
    yield fs.readFile(concent,"utf-8")

}
let it = read()
it.next().value.then(data=>{
    // console.log(data)  
    // console.log(it.next(data)) // { value: Promise { <pending> }, done: false }
    it.next(data).value.then(data=>{
        console.log(data)  
    })
})
// 输出结果 666

也可以这样

const fs = require("fs").promises;
function * read(){
    let concent = yield fs.readFile("./name.txt","utf-8")
    let age = yield fs.readFile(concent,"utf-8")
    return age
}
let it = read()
it.next().value.then(data=>{
    it.next(data).value.then(data=>{
        let r = it.next(data)
        console.log(r); // { value: '666', done: true }
    })
})

是不是感觉又陷入了 回调地狱

那么就用 co 吧

安装 co npm i co

用上来:

const fs = require("fs").promises;
function * read(){
    let concent = yield fs.readFile("./name.txt","utf-8");
    let age = yield fs.readFile(concent,"utf-8");
    return age;
}
let co = require("co")
co(read()).then(data=>{
    console.log(data); 
})
// 输出结果  666

是不是简单多了,爽不爽

co 库可以实现自动迭代
既然是自动执行,那么 promise 的 executor 中先执行一次 it.next() 方法,返回 value 和 done;value是一个 pending 的 Promise;如果 done=false,说明还没有走完,继续在 value.then 的成功回调中执行下一次 next,即调用 read 方法;直到 done 为 true,走完所有代码,调用 resolve;中间有任何一次 next 异常,直接调用 reject,停止迭代

总结:

  • function 关键字与函数名之间有一个星号
  • 函数体内部使用 yield 语句,定义不同的内部状态
  • yield 会将函数分割成好多个部分,每产出一次,就暂停一次
  • Genenrator 是一个生成器,调用 Genenrator 函数,不会立即执行函数体,只是创建了一个迭代器对象,如上例中的 it 就是调用 read 这个 Generator 函数得到的一个迭代器
  • 迭代器有一个 next 方法,调用一次就会继续向下执行,直到遇到下一个 yield 或 `return
  • next() 方法可以带一个参数,该参数会被当做上一条 yield 语句的返回值,并赋值给 yield 前面等号前的变量
  • 每遇到一个 yield,就会返回一个 {value:xxx,done:bool} 的对象,然后暂停,返回的 value 就是跟在 yield 后面的返回值,done 表示这个 generator 是否已经执行结束了
  • 当遇到 return 时,return 后的值就是 value 值,done 此时就是 true
  • 函数末尾如果没有 return,就是隐含的 return undefined
  • 使用 co 库,可以自动的将 generator 迭代
  • co 执行会返回一个 promise,用 then 注册成功/失败回调
  • co 将迭代器 it 作为参数,这里每调用一次 read,就执行一次 next

async/await


被称为 异步解决 的终极方案

async、await 是什么?

async 顾名思义是 “异步” 的意思,async 用于声明一个函数是异步的。

而 await 从字面意思上是 “等待” 的意思,就是用于等待异步完成。通俗来说,就是 await 在这里等待 promise 返回结果了,再继续执行。并且 await 只能在 async 函数中使用

通常 async、await 都是跟随Promise一起使用的。
为什么这么说呢?因为 async 返回的都是一个 Promise 对象,同时 async 适用于任何类型的函数上。这样 await 得到的就是一个 Promise 对象(如果不是 Promise 对象的话那 async 返回的是什么就是什么);
注:await 不仅仅用于等 Promise 对象,它可以等任意表达式的结果,所以,await 后面实际是可以接普通函数调用或者直接量的(不演示了)

紧跟着上面的代码,再写一段

const fs = require("fs").promises;
async function read(){
    let concent = await fs.readFile("./name.txt","utf-8")
    let age = await fs.readFile(concent,"utf-8")
    return age
}
read().then(data=>{
    console.log(data)   // 666
})

是不是比上面的写法还爽呢?

await 命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try...catch 代码块中

如下:

async function myFunction() {
  try {
    await somethingThatReturnsAPromise();
  } catch (err) {
    console.log(err);
  }
}

// 另一种写法

async function myFunction() {
  await somethingThatReturnsAPromise().catch(function (err){
    console.log(err);
  });
}

总结:

async:

  • async 函数会返回一个 Promise 对象
  • 如果 async 函数中是 return 一个值,这个值就是 Promise 对象中 resolve 的值
  • 如果 async 函数中是 throw 一个值,这个值就是 Promise 对象中 reject 的值

await:

  • await 只能在 async 函数中使用
  • await 后面要跟一个promise对象
  • await等promise 返回结果后,在继续执行

这三种方法都是用来解决异步的,很很很重要

通常到公司面试的时候,面试官都会问到:

  • 说一下异步的发展流程
  • 说一下异步的解决方案

那么看完本章内容就派上大用场了哦!