1.回调地狱
多层回调函数的相互嵌套,就形成了回调地狱,如:
setTimeout(() => { // 第一层回调函数
console.log('延迟1秒后输出')
setTimeout(() => { // 第二层回调函数
console.log('延迟2秒后输出')
setTimeout(() => { // 第三层回调函数
console.log('延迟3秒后输出')
}, 3000)
}, 2000)
}, 1000)
回调地狱的缺点:
- 代码耦合性太强,牵一发而动全身,难以维护
- 大量的代码冗余相互嵌套,代码的可读性差
为了解决回调地狱的问题,ES6中新增了Promise概念。
2.Promise
Promise是一个构造函数
const p = new Promise()
new
出来的Promise
实例对象,代表一个异步操作
查看Promise实例
console.dir(promise)
- 可以看到
prototype
上有then()
方法
Promise的.then()方法
Promise.prototype
上挂载了.then()
方法,每一次new Promise()得到的实例对象,都可以通过原型链
的方式访问到.then()方法 如 p.then()。
.then(成功的回调函数,失败的回调函数)
p.then(result=>{},err=>{})
- .then()方法用来
预先
指定成功
和失败
的回调函数 - 调用.then()方法时,成功的回调函数是必选的,失败的回调函数是可选的
(1)基于回调函数读取文件
import fs from 'fs'
fs.readFile('./file/01.txt', 'utf8', (err1, res1) => {
if (err1) return console.log(res1.message)
console.log(res1) // 123 读取文件1成功
fs.readFile('./file/02.txt', 'utf8', (err2, res2) => {
if (err2) return console.log(err2.message)
console.log(res2) // 456 读取文件2成功
fs.readFile('./file/03.txt', 'utf8', (err3, res3) => {
if (err3) return console.log(err3.message)
console.log(res3) // 789 读取文件3成功
})
})
})
- 可以看到出现了回调地狱的问题
(2)基于Promise异步读取文件
由于node.js官方提供的fs模块仅支持以回调函数的方式读取文件,不支持pormise调用方式。因此,
需要先安装then-fs
第三方包,从而支持我们基于Promise方式读取文件内容
import thenFs from 'then-fs'
thenFs.readFile('./file/01.txt', 'utf8').then(res1 => { console.log(res1) })
thenFs.readFile('./file/02.txt', 'utf8').then(res2 => { console.log(res2) })
thenFs.readFile('./file/03.txt', 'utf8').then(res3 => { console.log(res3) })
- 调用then-fs提供的readFile()方法,可以异步读取文件内容,thenFs.readFile()的返回值是Promise实例对象
- 因此可以调用.then()为每个Promise异步操作指定成功和失败的回调函数
- 由于是异步操作,得到的顺序不一
(3)基于Promise的链式调用
import thenFs from "then-fs"
thenFs.readFile('./file/10.txt', 'utf8').catch(err => {
console.log(err.message)
}).then(r1 => {
console.log(r1)
return thenFs.readFile('./file/02.txt', 'utf8')
}).then(r2 => {
console.log(r2)
return thenFs.readFile('./file/03.txt', 'utf8')
}).then(r3 => {
console.log(r3)
})
- 在第一个.then()中返回了一个新的Promise实例对象,继续调用.then(),为上一个.then的返回值(新的Promise实例),指定成功之后的回调函数
- Promise的链式操作中如果发生了错误,可以使用
Promise.prototype.catch
方法进行捕获处理 - 如果不希望前面的错误导致后续的.then无法正常执行,则可以将.catch的调用提前(.catch放在最后的话前面产生的错误会导致后面.then无法被执行)
.then()方法的特性:
-
如果上一个.then()方法中返回了一个新的Promise实例对象,则可以通过下一个.then()继续进行处理。
-
通过.then()方法的链式调用,解决回调地狱问题
(4)Promise.all()
Promise.all()
方法会发起并行
的Promise异步操作,等所有的异步操作全部结束后,才会执行下一步的.then操作 (等待机制
)。
import thenFs from "then-fs"
const promiseArr = [
// 三个异步读文件的promise实例
thenFs.readFile('./file/01.txt', 'utf8'),
thenFs.readFile('./file/02.txt', 'utf8'),
thenFs.readFile('./file/03.txt', 'utf8')
]
Promise.all(promiseArr).then(res => { // promise实例的顺序就是最终拿到的结果的顺序
console.log(res) // ['123','456','789']
}).catch(err => {
console.log(err.message)
})
- Promise.all() 等待所有的异步操作完成,拿到所有操作的结果
(5)Promise.race()
Promise.race()
方法会发起并行的Promise异步操作,只要任何一个异步操作完成,就会立即执行下一步的.then操作 (赛跑机制
)。
import thenFs from "then-fs"
const promiseArr = [
// 三个异步读文件的promise实例
thenFs.readFile('./file/01.txt', 'utf8'),
thenFs.readFile('./file/02.txt', 'utf8'),
thenFs.readFile('./file/03.txt', 'utf8')
]
Promise.race(promiseArr).then(res => { // 只要任何一个异步操作完成,就立即执行成功的回调函数
console.log(res)
}).catch(err => {
console.log(err.message)
})
- Promise.race() 哪个异步操作执行得快,就拿它的结果(得到执行最快的异步操作)
(6)基于Promise封装异步读文件的方法
import fs from 'fs'
function getFile(fpath) {
// resolve是成功的回调函数,reject是失败的回调函数
return new Promise(function (resolve, reject) { // 形式上的异步操作
fs.readFile(fpath, 'utf8', (err, dataStr) => { // 具体的异步操作
if (err) return reject(err) // 如果读取失败,则调用失败的回调函数
return resolve(dataStr) // 如果读取成功,则调用成功的回调函数
})
})
}
getFile('./file/01.txt').then((res1) => { console.log(res1) }).catch(err => { console.log(err.message) })
- 方法接收一个形参
fpath
,代表要读取文件的路径 - 方法的返回值为Promise实例对象
new Promise()
只是创建了一个形式上的异步操作- 如果想要创建具体的异步操作,则需要在new Promise()构造函数期间,传递一个function()函数, 将具体的异步操作定义到function函数内部
3.async/await
async
/await
是ES8引入的新语法,用来简化
Promise异步操作。在async/await出现之前,我们只能通过链式.then()方式处理promise异步操作。
链式.then()调用的优点:
- 解决了回调地狱的问题
链式.then()调用的缺点:
- 代码冗余,阅读性差,不易理解
(1)使用async/await简化Promise异步操作
import thenFs from "then-fs"
async function getFile() {
const r1 = await thenFs.readFile('./file/01.txt', 'utf8')
console.log(r1)
const r2 = await thenFs.readFile('./file/02.txt', 'utf8')
console.log(r2)
const r3 = await thenFs.readFile('./file/03.txt', 'utf8')
console.log(r3)
}
getFile()
- 如果方法内部用到了
await
,这个方法就必须被async
修饰 - 加了await后,thenFs.readFile()返回的就不再是promise实例,而是得到了异步操作后具体的内容
- 有了async和await就不需要.then()来获得最后的结果了
(2)async和await的注意事项
import thenFs from "then-fs"
console.log('A')
async function getFile() {
console.log('B')
const r1 = await thenFs.readFile('./file/01.txt', 'utf8')
const r2 = await thenFs.readFile('./file/02.txt', 'utf8')
const r3 = await thenFs.readFile('./file/03.txt', 'utf8')
console.log(r1)
console.log(r2)
console.log(r3)
console.log('D')
}
getFile() // A B C 123 456 789 D
console.log('C')
- 在async方法中,第一个await之前执行的代码会同步执行,await之后执行的代码会异步执行