Promise
什么是Promise
- Promise是ECMAScript中新增的一个API,是一个构造函数,从ES6开始支持
- Promise本身不是异步,一经创建就立即执行,但是它内部往往封装一个异步对象
- Promise的功能是可以将复杂的异步处理轻松的进行模块化
Promise的执行机制
为什么要使用Promise
不使用Promise读文件
- 在node中,使用
readFile()
对文件进行读取属于异步操作,如果在一个模块中多次使用该方法读取文件可能会造成读取结果无法预料的情况 - 文件结构
- 文件三个文件对用的内容
- a: hello a
- b: hello b
- c: hello c
- 如下:
// 引入文件系统
const fs = require('fs')
fs.readFile('./data/a.txt', 'utf8', (err, data) => {
if (err) {
return console.log('读取失败')
}
console.log(data)
})
fs.readFile('./data/b.txt', 'utf8', (err, data) => {
if (err) {
return console.log('读取失败')
}
console.log(data)
})
fs.readFile('./data/c.txt', 'utf8', (err, data) => {
if (err) {
return console.log('读取失败')
}
console.log(data)
})
- 按照正常的思路以及本身的需求,三次读取文件打印出来的应该一次是a、b、c三个文件中的内容,但是实际上真的是这样吗?让我们来看一下结果
# 第一种结果
PS C:\Users\yeShang\promise> node .\demo-promise.js
hello a
hello b
hello c
# 第二种结果
PS C:\Users\yeShang\promise> node .\demo-promise.js
hello b
hello c
hello a
# 第三种结果
PS C:\Users\yeShang\promise> node .\demo-promise.js
hello c
hello b
hello a
# 我这里只截取了三种不同情况的结果,如果各位在自己试的时候没有出现上述情况的一种或两种,那么别急,将你的程序多执行几次,总会出来的
-
很显然,这样的结果不是我们想要的,因为他不可预测,谁也不知道下一次出来的结果是什么
-
如何在不使用Promise的情况下解决这种问题,也就是让最终输出结果跟我们预想的一样,依次打印出来a、b、c三个文件中的内容
-
具体代码如下:
fs.readFile('./data/a.txt', 'utf8', (err, data) => {
if (err) {
return console.log('读取失败')
}
console.log(data)
fs.readFile('./data/b.txt', 'utf8', (err, data) => {
if (err) {
return console.log('读取失败')
}
console.log(data)
fs.readFile('./data/c.txt', 'utf8', (err, data) => {
if (err) {
return console.log('读取失败')
}
console.log(data)
})
})
})
- 执行结果如下:
PS C:\Users\yeShang\promise> node .\demo-promise.js
hello a
hello b
hello c
# 你放心,这种方法不管你试多少次,结果就只有这一种
- 这里使用的是多层嵌套的方法
- 因为第一层执行完成后才会去执行第二层,然后执行第三层,依次一直执行下去
- 正是有这个执行顺序,结果才会固定
- 但是这样做也存在一个问题,这里只是读取三个文件,那如果是要读取十几二十几个文件呢?就会出现我们常说的回调地狱(也称‘回调金字塔’),代码样式难看是小事,重要的是最终的代码会变得难读和让其他人难以维护
- 由此,Promise应运而生
如何使用Promise
- 创建一个Promise对象
// Promies 是一个构造函数,使用的时候需要先创建一个实例对象
const p1 = new Promise((resolve, reject) => {
// resolve表示成功
// reject表示失败
})
- 使用Promise解决上述的读文件的问题
- 具体代码如下:
// 引入文件系统
const fs = require('fs')
const p1 = new Promise((resolve, reject) => {
fs.readFile('./data/a.txt', 'utf8', (err, data) => {
if (err) {
reject(err)
}else {
resolve(data)
}
})
})
const p2 = new Promise((resolve, reject) => {
fs.readFile('./data/b.txt', 'utf8', (err, data) => {
if (err) {
reject(err)
}else {
resolve(data)
}
})
})
const p3 = new Promise((resolve, reject) => {
fs.readFile('./data/c.txt', 'utf8', (err, data) => {
if (err) {
reject(err)
}else {
resolve(data)
}
})
})
p1
.then((data) => {
console.log(data)
return p2
}, (err) => {
console.log(err)
})
.then((data) => {
console.log(data)
return p3
}, (err) => {
console.log(err)
})
.then((data) => {
console.log(data)
}, (err) => {
console.log(err)
})
- 在上述代码中,p1是第一个Promise对象
p1.then()
中的两个回调函数- 第一个表示当前Promise对象返回的成功的结果
- 第二个表示当前Promise对象返回的错误的结果
then()
可以返回一个新的Promise对象,用下一个then()
来接收- 上述代码的执行结果如下
PS C:\Users\yeShang\promise> node .\demo-promise.js
hello a
hello b
hello c
# 你尽管试,试出来不同结果算我输
# 通过then()方法,依次返回三个Promise对象,其执行结果只能是前一个执行后返回值给第二个then()方法,第二个then()方法接收后才能执行,所以不会出现结构不可预测的情况
- 上述代码是为了易读,存在很大的冗余,不够优雅,下边是重构后的代码
// 引入文件系统
const fs = require('fs')
// 在上边的代码中,三个Promose读取对象只有读取文件的路径不同,这里对其进行封装
const myReadFile = filePath => {
// 返回一个Promise对象
return new Promise((resolve, reject) => {
fs.readFile(filePath, 'utf8', (err, data) => {
if (err) {
reject(err)
} else {
resolve(data)
}
})
})
}
myReadFile('./data/a.txt')
.then((data) => {
console.log(data)
return myReadFile('./data/b.txt')
}, (err) => {
console.log(err)
})
.then((data) => {
console.log(data)
return myReadFile('./data/c.txt')
}, (err) => {
console.log(err)
})
.then((data) => {
console.log(data)
}, (err) => {
console.log(err)
})
- 执行结果与上边相同,结果如下:
PS C:\Users\yeShang\promise> node .\demo-promise.js
hello a
hello b
hello c
- 执行结果与上边相同,结果如下:
PS C:\Users\yeShang\promise> node .\demo-promise.js
hello a
hello b
hello c
- Promise的常用操作就是这些了,如果有说的不对的地方,望指正,谢谢!