场景:异步函数的依次顺序执行
同时出现多个异步函数,执行顺序并不是按照代码的上下文顺序执行。同步任务是自上而下,但是异步任务并不是,在被放到eventLoop之后,执行顺序会因为各种因素导致不一样,比如说读取文件的大小。
- 代码示例,在nodejs环境下进行文件读取,这是个异步操作,下面的代码是按照顺序自上而下书写的,但是结果并不是按照顺序完成,并且会发现每次输出结果都不一样。
const fs = require('fs')
const path = require('path')
fs.readFile(path.join(__dirname, './files/a.txt'), 'utf-8', (err, data) => {
console.log('1----', data)})
fs.readFile(path.join(__dirname, './files/b.txt'), 'utf-8', (err, data) => {
console.log('2----', data)})
fs.readFile(path.join(__dirname, './files/c.txt'), 'utf-8', (err, data) => {
console.log('3----', data)})
fs.readFile(path.join(__dirname, './files/d.txt'), 'utf-8', (err, data) => {
console.log('4----', data)})
- 结果:
那我们在不使用promise的情况下,如何手动实现按照顺序执行
- 逐个嵌套,只有上一个执行完毕/成功之后再执行下一个
// 依次读取4个文件
const fs = require('fs')
const path = require('path')
fs.readFile(path.join(__dirname, './files/a.txt'), 'utf-8', (err, data) => {
console.log('1----', data)
fs.readFile(path.join(__dirname, './files/b.txt'), 'utf-8', (err, data) => {
console.log('2----', data)
fs.readFile(path.join(__dirname, './files/c.txt'), 'utf-8', (err, data) => {
console.log('3----', data)
fs.readFile(path.join(__dirname, './files/d.txt'), 'utf-8', (err, data) => {
console.log('4----', data)
})
})
})
})
- 结果:
- 但是这样的话又出现了一个问题,小批量的我们可以这样写,但是如果大批量的处理,是不是要无限嵌套下去,会形成回调地狱,这显然是不合理的。所以有了promise
自用的promise理解函数
const fs = require('fs')
const path = require('path')
// 使用 new 来创建一个 Promise 对象
const p = new Promise(
// 这是 Promise 的参数(固定的)
// - resolve 参数:返回成功的数据
// - reject 参数:返回失败的数据
(resolve, reject) => {
// 异步行为放在这里
fs.readFile(path.join(__dirname, './files/a.txt'), 'utf-8', (err, data) => {
// 如果出错,则使用 reject 返回错误信息
if (err) {
reject(err)
}
// 如果成功,则使用 resolve 返回成功的数据
else {
resolve(data)
}
})
}
)
// 通过 Promise 对象,处理成功的情况
p.then((data) => {
console.log(data)
})
p.catch((err) => {
console.log("出错啦", err)
})
- 总结
- 我们新建的一个promise对象,接收一个参数,这个参数是一个回调函数,回调函数里面接收两个方法:resolve,reject。我们在这个回调函数里执行异步操作,异步操作会有返回,如果成功的话,执行resolve方法并把成功的数据返回出去。如果失败的话,执行reject方法,把失败的错误信息返回出去。
- 在promise里面返回的数据,通过resolve(成功)返回的数据,我们使用对象.then((data)=>{})的方式接收并进行下一步操作。通过reject(失败)返回的错误信息,我们使用对象.catch((err)=>{})的方式接收。
- 特性
- promise对象有三个状态:
- Pending 初始状态,进行中,既不是成功也不是失败。
- Resolved 成功状态或者完成状态,也叫Fulfilled
- Rejected 失败状态或者未完成状态
- 这三种状态变化,成功的话,pending变成resolved,失败的话,pending变成rejected。状态一旦改变就无法再次改变状态,这是promise(承诺)的由来,一个promise对象只能被修改一次。
- 成功了进入.then中,状态改为resolved,失败进入.catch中,状态改为rejected。
再次实现读取文件
// 依次读取4个文件
const fs = require('fs')
const path = require('path')
function readMyFile(fileName) {
// 创建 Promise 异步处理对象
const p = new Promise(
(resolve, reject) => {
fs.readFile(path.join(__dirname, './files', fileName), 'utf-8', (err, data) => {
if (err) {
reject(err)
} else {
resolve(data)
}
})
}
)
// 返回 Promise 对象
return p
}
// a.txt 1
// b.txt 3
// c.txt 2
// d.txt 5
readMyFile('a.txt')
.then(data => {
console.log('1----', data)
// 返回一个 Promise,这个 Promise 可以被下一个 .then 处理
return readMyFile('b.txt')
})
.then(data => {
console.log('2----', data)
return readMyFile('c.txt')
})
.then(data => {
console.log('3----', data)
return readMyFile('d.txt')
})
.then(data => {
console.log('4----', data)
})
- 结果:
- 注意
- 在.then 里return 出来的又是一个promise对象,可以继续.then()调用,可以被下一个.then()处理。
- 当返回一个非promise对象数据时,则下一个then的回调函数参数得到的就是这个值
- 当返回一个promise对象时,则下一个then的回调函数参数得到的就是当前这个promise resolve出来的值
readMyFile('a.txt').then(data => {
console.log('>>>>', data)
// 当返回一个非 Promise 对象数据时,则下一个 then 的回调函数参数得到就是该值
return 123
// 当返回一个 Promise 对象时,则下一个 then 的回调函数参数得到的就是当前这个 Promise resolve 出来的值
// return readMyFile('b.txt')
}).then(data => {
console.log('>>>2>>>>', data)
})
- 各自独立读取也可以实现
// 各自为政,自己执行完自己打印
readMyFile('a.txt').then(data => {
console.log(data)
})
readMyFile('b.txt').then(data => {
console.log(data)
})
readMyFile('c.txt').then(data => {
console.log(data)
})
readMyFile('d.txt').then(data => {
console.log(data)
})
promise的两个方法
- Promise.all 同时读取,等待所有文件全部读取完成,再执行成功结果。同时进行多个promise异步操作,所有的promise都完成之后才执行。
// 同时读取,等待所有文件都读完,一次性打印
const p = Promise.all([
readMyFile('a.txt'),
readMyFile('b.txt'),
readMyFile('c.txt'),
readMyFile('d.txt'),
])
p.then(results => {
console.log(results)
})
- Promise.race 同时读取,同时执行多个异步函数,哪个异步成功了就直接执行.then,其他的就不管了。跟单词的意思一样,赛跑机制。
const p = Promise.race([
readMyFile('a.txt'),
readMyFile('b.txt'),
readMyFile('c.txt'),
readMyFile('d.txt'),
])
p.then(data => {
console.log(data)
})
- 不难看出,每次执行同一段代码,结果是不一样的,每次速度都不一样