Promise
回调地狱
回调地狱
多层回调函数的相互嵌套,就形成了回调地狱。示例代码如:回调地狱更多导致的问题还是:难以阅读和维护,通常出现在异步操作中
其和递归问题导致的栈溢出还是不同的,两者是不同的问题1
2
3
4
5
6
7
8
9
10
11
12
13setTimeout( () =>{ //第1层回调函数
console.log( '延时1秒后输出')
setTimeout(() => { //第2层回调函数
console.log('再延时2秒后输出')
setTimeout(() => { //第3层回调函数
console.log('再延时3秒后输出')
},3000)
},2000)
},1000)回调地狱的缺点:
代码耦合性太强,牵一发而动全身,难以维护
大量冗余的代码相互嵌套,代码的可读性变差如何解决
为了解决回调地狱的问题,ES6 (ECMAScript 2015)中新增了Promise的概念。解决回调地狱的方法包括使用 Promise、async/await 等异步编程技术,以及使用函数式编程的方式来组织代码,使其更具可读性和可维护性。
Promise基本概念
Promise是一个构造函数
- 我们可以创建Promise 的实例
const p = newPromise()
- new出来的Promise 实例对象,代表一个异步操作
- 我们可以创建Promise 的实例
Promise.prototype 上包含一个.then()方法
- 每一次new Promise()构造函数得到的实例对象,
- 都可以通过原型链的方式访问到.then()方法,例如
p.then()
.then()方法用来预先指定成功和失败的回调函数
- p.then(成功的回调函数,失败的回调函数)
- p.then(result,=> {}, error => {})
- 调用.then()方法时,成功的回调函数是必选的、失败的回调函数是可选的
基于 then-fs 异步的读取文件内容
基于回调函数按顺序读取文件内容
虽然可以保证文件的读取顺序,但是形成了一个回调地狱的问题基于 then-fs 读取文件内容
fs模块仅支持 以回调函数的方式 读取文件,不支持Promise的调用方式。因此,需要先安装 then-fs 第三方包,从而支持基于Promise的方式读取文件的内容:npm install then-fs
- then-fs 的基本使用
调用 then-fs 提供的readFile()方法,可以异步地读取文件的内容,它的返回值是 Promise 的实例对象。因此可以调用.then()方法为每个 Promise 异步操作指定成功和失败之后的回调函数。示例代码如下:当前只是异步的去读文件,无法保证文件的读取顺序,多次执行读取顺序不一1
2
3
4
5
6// 基于Promise的方式读取文件
import thenFs from 'then-fs'
// .then()中的失败回调是可选的,这里省略了
thenFs.readFile('./files/1.txt','utf8').then((r1)=>{console.log(r1);})
thenFs.readFile('./files/2.txt','utf8').then((r2)=>{console.log(r2);})
thenFs.readFile('./files/3.txt','utf8').then((r3)=>{console.log(r3);})
基于Promise按顺序读取文件的内容
.then方法的特性
如果上一个.then()方法中返回了一个新的Promise实例对象,则可以通过下一个.then()继续进行处理。通过.then()方法的链式调用,就解决了回调地狱的问题。基于Promise按顺序读取文件的内容
只要任何方法返回值是一个Promise实例对象,那就都可以继续调用.then()方法1
2
3
4
5
6
7
8
9
10
11
12import thenFs from 'then-fs'
thenFs.readFile('./files/1.txt', 'utf8')// 返回值是Promise的实例对象
.then((r1) => { // 通过.then 为返回的promise实例指定成功之后的回调函数
console.log(r1)
return thenFs.readFile('./files/2.txt', 'utf8')
}).then((r2) => {
console.log(r2)
return thenFs.readFile('./files/3.txt', 'utf8')
}).then((r3) => {
console.log(r3)
})通过 .catch 捕获错误
在 Promise 的链式操作中如果发生了错误,可以使用Promise.prototype.catch方法进行捕获和处理:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import thenFs from 'then-fs'
thenFs.readFile('./files/11.txt', 'utf8') // 文件不存在
.then((r1) => {
console.log(r1)
return thenFs.readFile('./files/2.txt', 'utf8')
})
.then((r2) => {
console.log(r2)
return thenFs.readFile('./files/3.txt', 'utf8')
})
.then((r3) => {
console.log(r3)
})
.catch((err) => {
console.log(err.message)
}) // 只返回错误,没有读取的文件内容如果不希望前面的错误导致后续的.then无法正常执行,可以将.catch的调用提前:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import thenFs from 'then-fs'
thenFs.readFile('./files/11.txt', 'utf8')
.catch((err) => {
console.log(err.message)
})
.then((r1) => {
console.log(r1)
return thenFs.readFile('./files/2.txt', 'utf8')
})
.then((r2) => {
console.log(r2)
return thenFs.readFile('./files/3.txt', 'utf8')
})
.then((r3) => {
console.log(r3)
}) // 先打印错误,然后打印读取内容,r1为undefined,r2r3读取成功
Promise.all()方法
Promise.all()方法会发起并行的Promise异步操作,等所有的异步操作全部结束后才会执行下一步的.then操作(等待机制)。示例代码如下:
1 | import thenFs from 'then-fs' |
Promise示例的顺序,就是最终拿到的结果的顺序。如果有一个文件读取错误,所有数据都拿不到
Promise.race() 方法
Promise.race()方法会发起并行的Promise异步操作,只要任何一个异步操作完成,就立即执行下一步的.then操作(赛跑机制)。示例代码如下:
1 | import thenFs from 'then-fs' |
只会拿到一个文件的读取结果,读取最快的那个,无法保证读取的文件是哪个
基于Promise封装异步读文件的方法
方法的封装要求:
- 方法的名称要定义为getFile
- 方法接收一个形参fpath,表示要读取的文件的路径
- 方法的返回值为Promise 实例对象
getFile方法的基本定义
1
2
3function getFile(fpath){
return new Promise()
}注意:new Promise() 只是创建了一个形式上的异步操作
创建具体的异步操作
如果想要创建具体的异步操作,则需要在new Promise()构造函数期间,传递一个function函数,将具体的
异步操作定义到function函数内部。示例代码如下:1
2
3
4
5
6
7import fs from 'fs'
function getFile(fpath){
return new Promise(function(){
fs.readFile(fpath,'utf-8',(err,dataStr)=>{ })
})
}获取.then的两个实参
通过.then()指定的成功和失败的回调函数,可以在 function的形参中 进行接收,示例代码如下:调用 resolve 和 reject 回调函数
Promise异步操作的结果,可以调用 resolve 或 reject 回调函数进行处理。示例代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14import fs from 'fs'
function getFile(fpath) {
return new Promise(function (resolve,reject) {
fs.readFile(fpath, 'utf-8', (err, dataStr) => {
if(err) return reject(err) // 发生错误
resolve(dataStr) // 读取成功
})
})
}
getFile('./files/1.txt').then((r1)=>{console.log(r1)},(err)=>{console.log(err.message);})
// 失败的回调函数可以省略不写,可以用catch进行捕获
getFile('./files/1.txt').then((r1)=>{console.log(r1)}).catch((err)=>{console.log(err.message)})