前言
这篇文章主要是探究 Promise 的实现原理,对于使用方法,这里不做过多概述,如果还有对 Promise 使用方式不太了解的,可以先看 阮一峰老师的 Promise 教程
Promise 是什么,为什么会出现 Promise?
- 抽象表达:
- Promise 是一门新的技术(ES6规范)
- Promise 是js 中进行异步变成的新解决方案,在没有 Promise 之前,旧方案是单纯使用回调函数
- 具体表达:
- 从语法上来说: promise 是一个构造函数
- 从功能上来说:promise 对象用来封装一个异步操作并可以获取其成功/失败的结果值
- 作用:
- Promise 主要是用来解决回调地狱的问题,在没有Promise 之前,如果异步任务比较多,并且有相互依赖的作用,就只能使用回调函数的方式来处理,这样就会形成回调地狱,代码可读性和可维护性会很差。
- Promise 的 then 方法支持链式调用,很好的解决了之前使用回调函数的书写方式,使代码逻辑很有条理
- 但是Promise 也是有缺点的,首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
Promise 的状态改变
promise 有三种状态 pending (初始状态,或者说是执行中的状态)
, resolved || fulfilled (执行完成,成功的状态,下文中将使用 fulfilled 作为成功的状态)
rejected (执行完成,失败的状态)
状态的改变只有两种方式 pending => fulfilled 或者 pending => rejected
, 并且状态一经改变完成,就不可逆。成功的结果数据一般称为 value
失败的结果数据一般称为 reason
Promise 的基本流程如下图
以上对 Promise 做了一个简单的概述,下边我们来实现一个自己的 Promise
Promise 手动实现
首先我们先看下 原生的 Promise 实例对象中都包含的内容:
通过打印看到实例对象的原型上 包含 catch、finally、then、和 constructor 构造函数
最下边还有两个双方括号包含的内置属性 PromiseState、对应的就是Pormise 的状态 pending/ fulfilled/rejected 。 PromiseResult 对应的就是执行完成后,成功或失败的结果,
下边我们根据上图的内容,和 Promise API 先搭建一下 Promise 的整体框架代码如下:
// 这里直接声明一个 Promise 类可以覆盖掉 原生 Promise
class Promise {
constructor (executor) {
// 添加初始属性
this.PromiseState = 'pending'
this.PromiseResult = null
// 声明一个栈,用来存放then方法中的回调,onResolved, onRejected,之所以用栈,是因为
// 同一个实例 可以同时制定多个 then 方法,也就会有多个回调
this.callbacks = []
// 保存实例对象 this
const self = this
// resolve 函数
function resolve (data) {
}
// reject 函数
function reject (data) {
}
// 执行器函数
executor(resolve, reject)
}
// 添加 then 方法 === es5 Promise.prototype.then () {} 方法
then (onResolved, onRejected) {
}
// 添加 catch 方法 === es5 Promise.prototype.catch () {} 方法
catch (onRejected) {
}
// 添加 resolve 方法 === es5 Promise.resolve () {} 方法
static resolve (value) {
}
// 添加 reject 方法 === es5 Promise.reject () {} 方法
static reject (reason) {
}
// 添加 all 方法 === es5 Promise.all () {} 方法
static all (promises) {
}
// 添加 race 方法 === es5 Promise.race () {} 方法
static race (promises) {
}
}
上边的代码中 static 关键字 static 是ES6 class 中的新语法,用来声明 类中的静态方法
上边的 constructor(executor) 中的 executor 参数是执行器函数作为参数,其实就是下边代码中实例化 Promise 对象中的函数中的 参数 (resolve, reject) => {}
这部分内容,这个函数参数中有两个函数,第一个习惯上叫做 resolve 是成功的状态下执行的函数, 第二个是 reject 是失败状态下执行的函数
let p = new Promise((resolve, reject) => {
resolve('Ok')
})
接下来我们开始一步步的完善上边的 Promise 类
1、完善 constructor 中的 resolve() 和 reject() 函数
首先来看下这段代码
let p = new Promise((resolve, reject) => {
resolve('Ok')
// reject('Error')
// throw 'Error' // 这里抛出错误,错误信息 也会赋值给 PromiseResult 属性,对应的 PromiseState 状态变为 rejected
// 使用定时器 来模拟异步操作,执行成功后调用 resolve, 失败调用 reject
// setTimeout(() => {
// // resolve('OK')
// reject('Error')
// }, 1000)
})
console.log(p)
首先根据上边的示例来写 constructor 中的 resolve() 和 reject()
// 直接覆盖掉 原生 promise
class Promise {
constructor (executor) {
// 添加初始属性
this.PromiseState = 'pending'
this.PromiseResult = null
// 声明一个栈,用来存放then方法中的回调,onResolved, onRejected,之所以用栈,是因为
// 同一个实例 可以同时制定多个 then 方法,也就会有多个回调
this.callbacks = []
// 保存实例对象 this
const self = this
// resolve 函数
function resolve (data) {
// 修改状态前,先判断
if (self.PromiseState !== 'pending') return
// 1.修改对象状态 (promiseSate)
self.PromiseState = 'fulfilled' // resolved
// 2.设置对象结果值 (promiseResult)
self.PromiseResult = data
}
// reject 函数
function reject (data) {
// 修改状态前,先判断
if (self.PromiseState !== 'pending') return
// 1.修改对象状态 (promiseSate)
self.PromiseState = 'rejected'
// 2.设置对象结果值 (promiseResult)
self.PromiseResult = data
}
// 执行器函数
// 这里使用 try catch 是为了捕获执行器执行的时候抛出异常
try {
// 同步调用[执行器函数(也就是 executor)]
executor(resolve, reject)
} catch (e) {
// 修改 Promise 对象状态为 失败
reject(e)
}
...
...
}
上边我们说过改变 Promise 状态的两种方式是
pending => fulfilled 或者 pending => rejected
, 并且状态一经改变完成,就不可逆
所以在 resolve 和 reject 函数中要先判断PromiseState
如果不是初始状态pending
的情况下直接return
不继续向下执行.
2、完善 原型上的 then 方法
先看这段代码
let p = new Promise((resolve, reject) => {
resolve('Ok')
// reject('Error')
// setTimeout(() => {
// // resolve('OK')
// reject('Error')
// }, 1000)
})
p.then(value => {
console.log(value) // Ok
}, reason => {
console.warn(reason) // 调用 reject 这里 reason 就是 Error
})
// 下边的注释打开以后,就等于是给 实例对象 p 指定了 多个 then 方法,这种情况下,多个 then 方法都会被执行
// p.then(value => {
// alert(value)
// }, reason => {
// alert(reason)
// })
我们都知道 实例对象的 then 方法中接受两个回调函数,第一个是成功的回调函数,第二个是失败的回调函数,下边我们来继续完善 then 方法
...
...
// 添加 then 方法
then (onResolved, onRejected) {
const self = this
// 判断调用 then 方法的时候,回调函数参数 onRejected 没有传的情况,给 onRejected 指定一个函数,如果执行失败,就会走这个指定的
// onRejected 函数,然后抛出错误,链式调用最后边的 catch 就会捕获到
if (typeof onRejected !== 'function') {
onRejected = reason => {
throw reason
}
}
// 判断如果调用 then 方法的时候什么都不传,并且后边还跟着 then 方法的情况,给 onResolved 指定一个函数执行,这样可以让后边的 then 不受影响继续向下执行
if (typeof onResolved !== 'function') {
onResolved = value => value
}
// 这里之所以需要 return Promise 是因为,then 方法调用也会有返回Promise对象
// 返回的 Promise 对象中的 PromiseResult 的值取决于 传入进来的 onResolved 回调函数执行后的返回值
// 返回的 Promise 对象中的 PromiseState 的值取决于:
// 执行器中的回调函数是同步操作:PromiseState = 执行器中调用的回调函数 是 resolve(fulfilled) 还是 reject(rejected)
//
return new Promise((resolve, reject) => {
// 封装函数
function callback(type) {
// 这里的 try catch 是为了防止实例对象调用 then 方法中的两个回调 onResolved, onRejected 函数中抛出错误的时候可以继续执行代码,并捕获错误信息
try {
// 获取回调函数的执行结果
let result = type(self.PromiseResult)
// 判断
// 如果实例中调用 then 方法的返回值也是一个 Promise对象,这里的 result等于也就是一个 Promise 对象的实例
// 然后继续调用实例上的 then 方法执行对应的 resolve() 和 reject 就可以,这样就会重新去执行 Promise 构造函数中的
// resolve 或者 reject 方法
if (result instanceof Promise) {
// 如果是Promise 类型的对象
result.then(v => {
resolve(v)
}, r => {
reject(r)
})
} else {
// 结果的对象状态为 [成功]
resolve(result)
}
} catch (e) {
reject(e)
}
}
// 调用回调函数,先判断 PromiseState 状态
// 这里是同步的状态下,已经修改过状态,并修改了结果以后,执行到 then 的操作
// 所以要先判断状态
if (this.PromiseState === 'fulfilled') {
// 这里要用 setTimeout 包裹一下,使其变为异步任务,把任务放进队列
setTimeout(() => {
callback(onResolved)
})
}
if (this.PromiseState === 'rejected') {
// 这里要用 setTimeout 包裹一下,使其变为异步任务,把任务放进队列
setTimeout(() => {
callback(onRejected)
})
}
// 这里是异步执行的状态下,状态还是 pending 没有被修改过,说明异步执行还在执行过程中,
// 然后先执行到了 then 方法。这里需要先把 onResolved, onRejected 两个回调函数先存一下
// 等到异步执行完成,调用了 resolve || reject 的时候,在 resolve || reject 函数中再去判断
// callback 对象中是否有存储回调函数,有的话在执行回调函数
if (this.PromiseState === 'pending') {
this.callbacks.push({
onResolved: function () {
callback(onResolved)
},
onRejected: function () {
callback(onRejected)
}
})
}
})
}
...
...
完善了 then 方法以后,还要对应的在继续完善下 constructor 中的 resolve reject 方法 ,因为上边的代码最后部分 我们可以看到 ,如果给实例对象 p 指定多个 then 方法的情况下,onResolved / onRejected回调函数会被 追加到 callbacks 栈中,在 constructor 中的 resolve reject 方法是 执行完成后调用的方法,这两个方法执行的最后也要对应的执行下 callbacks 栈中的回调函数
代码如下:
constructor (executor) {
...
...
// resolve 函数
function resolve (data) {
// 修改状态前,先判断
if (self.PromiseState !== 'pending') return
// 1.修改对象状态 (promiseSate)
self.PromiseState = 'fulfilled' // resolved
// 2.设置对象结果值 (promiseResult)
self.PromiseResult = data
// Promise实例中,异步执行完成,执行回调
setTimeout(() => {
self.callbacks.forEach(item => {
item.onResolved(data)
})
})
}
// reject 函数
function reject (data) {
// 修改状态前,先判断
if (self.PromiseState !== 'pending') return
// 1.修改对象状态 (promiseSate)
self.PromiseState = 'rejected'
// 2.设置对象结果值 (promiseResult)
self.PromiseResult = data
// Promise实例中,异步执行完成,执行回调
setTimeout(() => {
self.callbacks.forEach(item => {
item.onRejected(data)
})
})
}
...
...
}
上边的内容不是很好理解,最好是根据 then 方法的特性,写一下示例运行下代码比较好。
完善原型上的 catch 方法
先看下代码示例
let p = new Promise((resolve, reject) => {
setTimeout(() => {
// resolve('Ok')
reject('Error')
}, 1000);
})
p.then(value => {
console.log(1111)
// throw 'Errr'
}).then(value => {
console.log(2222)
}).then(value => {
console.log(3333)
}).catch(reason => {
console.warn(reason)
})
上边的代码如果调用 resolve(‘Ok’) 下边then 方法上的执行顺序是从上到下依次执行的,如果调用的是 reject(‘Error’) 则上边的所有then 方法都不会执行,只执行最后的 catch 方法进行错误捕获。如果第一个then 中的 throw ‘Error’ 抛出错误以后,下边的两个 then 方法也不会执行,只执行最后的 catch 方法
这种特性叫做异常穿透,如果执行过程中任何一个环节出现错误,都会传到最后失败的回调中处理
下边完善 catch 方法, catch 方法的实现很简单,直接调用 then 方法,并且把 失败的 onRejected 回调函数传入即可
...
// 添加 catch 方法
catch (onRejected) {
return this.then(undefined, onRejected)
}
...
完善 Promise.resolve 方法
示例代码:
// const p = Promise.resolve('OK')
// console.log(p)
// const p2 = Promise.resolve(new Promise((resolve, reject) => {
// // resolve('success')
// reject('error')
// }))
// console.log(p2)
const p3 = Promise.resolve(Promise.resolve('Oh Yeah'))
console.log(p3)
静态方法 resolve() 完善
...
// 添加 resolve 方法
static resolve (value) {
// 返回 promise 对象
return new Promise((resolve, reject) => {
// 如果传入的 value 也是promise 实例, 则执行这个实例的 then 方法,然后执行对应的 resolve reject 方法
if (value instanceof Promise) {
value.then(v => {
resolve(v)
}, r => {
reject(r)
})
} else {
// 如果不是 promise 对象,直接设置成功
resolve(value)
}
})
}
...
因为 Promise.resolve 方法执行完成后也会返回一个 Promise 对象,所以这个静态方法中也要返回一个实例化后的 Promise 对象
完善Promise.reject() 方法
示例代码:
<script>
const p = Promise.reject('Error')
const p2 = Promise.reject(new Promise((resolve, reject) => {
resolve('Ok')
}))
console.log(p)
console.log(p2)
</script>
完善静态方法 reject
// 添加 reject 方法
static reject (reason) {
return new Promise((resolve, reject) => {
reject(reason)
})
}
因为 reject() 方法的作用就是执行失败的回调,并返回一个 Promise 对象,所以 reject 静态方法中直接返回 实例化的 Promise 对象,并调用这个对象的 reject 方法。把失败信息传入到 reject(reason)中
完善 Pomise.all() 方法
示例代码:
// let p1 = new Promise((resolve, reject) => {
// setTimeout(() => {
// resolve('OK')
// }, 1000);
// })
// let p2 = Promise.resolve('SUccess')
// let p3 = Promise.resolve('Oh Yeah')
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK')
}, 1000);
})
let p2 = Promise.reject('SUccess')
let p3 = Promise.resolve('Oh Yeah')
// 调用 all 方法
// debugger
let result = Promise.all([p1, p2, p3])
console.log(result)
all() 方法的特性:
- all 方法中接受一个数组,数组中的每一项都是一个 Promise 对象,数组中的这些 Promise 对象全部都执行成功后的 resolve() 方法,才会最终成功,状态变为 fulfilled 返回值也是一个数组,数组中的每一项对应 all([ ]) 方法数组中每个 Promise 对象返回的结果值。如果其中只要有一个失败,就会全部失败,状态直接变为 rejected, 返回值也会变为失败返回的值。
完善 all 方法
// 添加 all 方法
static all (promises) {
// 返回结果是 promise 对象
return new Promise((resolve, reject) => {
// 声明变量
let count = 0
let arr = []
// 遍历
for (let i = 0; i < promises.length; i++) {
//
promises[i].then(v => {
// 得知对象的状态是成功 每个 promise 对象都成功
count++
// 将当前 promise 对象成功的结果,存入到数组中,这里最好不好使用 push 添加,这样顺序有可能会乱,要使用下标的方式添加
arr[i] = v
// 确保 all 中所有的 Promise 对象都执行成功了,再去改变 promiseState 状态,再赋值
if (count === promises.length) {
resolve(arr)
}
}, r => {
reject(r)
})
}
})
}
完善 Promise.race() 静态方法
示例代码:
<script>
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK')
}, 0);
})
// let p2 = Promise.resolve('SUccess')
let p2 = Promise.reject('Error')
let p3 = Promise.resolve('Oh Yeah')
// 调用 all 方法
// debugger
let result = Promise.race([p1, p2, p3])
console.log(result)
</script>
race() 方法特性:
race 翻译后是赛跑的意思,这个方法中也是接受一个数组作为参数,数组的每一项都是一个 promise 对象。这些对象中最先执行完成并且成功的返回值会作为 PromiseResult 的值,状态变为 fulfilled 。 如果其中有一个失败的,这个失败的对象的 返回值 会作为 PromiseResult 的值,状态变为 rejected
// 添加 race 方法
static race (promises) {
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
promises[i].then(v => {
// 修改返回对象的状态为 [成功]
resolve(v)
}, r => {
// 修改返回对象的状态为 [失败]
reject(r)
})
}
})
}
至此 Promise 封装完成。完整代码如下
// 直接覆盖掉 原生 promise
class Promise {
constructor (executor) {
// 添加初始属性
this.PromiseState = 'pending'
this.PromiseResult = null
// 声明一个栈,用来存放then方法中的回调,onResolved, onRejected,之所以用栈,是因为
// 同一个实例 可以同时制定多个 then 方法,也就会有多个回调
this.callbacks = []
// 保存实例对象 this
const self = this
// resolve 函数
function resolve (data) {
// 修改状态前,先判断
if (self.PromiseState !== 'pending') return
// 1.修改对象状态 (promiseSate)
self.PromiseState = 'fulfilled' // resolved
// 2.设置对象结果值 (promiseResult)
self.PromiseResult = data
// Promise实例中,异步执行完成,执行回调
setTimeout(() => {
self.callbacks.forEach(item => {
item.onResolved(data)
})
})
}
// reject 函数
function reject (data) {
// 修改状态前,先判断
if (self.PromiseState !== 'pending') return
// 1.修改对象状态 (promiseSate)
self.PromiseState = 'rejected'
// 2.设置对象结果值 (promiseResult)
self.PromiseResult = data
// Promise实例中,异步执行完成,执行回调
setTimeout(() => {
self.callbacks.forEach(item => {
item.onRejected(data)
})
})
}
// 这里使用 try catch 是为了捕获执行器执行的时候抛出异常
try {
// 同步调用[执行器函数(也就是 executor)]
executor(resolve, reject)
} catch (e) {
// 修改 Promise 对象状态为 失败
reject(e)
}
}
// 添加 then 方法
then (onResolved, onRejected) {
const self = this
// 判断调用 then 方法的时候,回调函数参数 onRejected 没有传的情况,给 onRejected 指定一个函数,如果执行失败,就会走这个指定的
// onRejected 函数,然后抛出错误,链式调用最后边的 catch 就会捕获到
if (typeof onRejected !== 'function') {
onRejected = reason => {
throw reason
}
}
// 判断如果调用 then 方法的时候什么都不传,并且后边还跟着 then 方法的情况,给 onResolved 指定一个函数执行,这样可以让后边的 then 不受影响继续向下执行
if (typeof onResolved !== 'function') {
onResolved = value => value
}
// 这里之所以需要 return Promise 是因为,then 方法调用也会有返回Promise对象
// 返回的 Promise 对象中的 PromiseResult 的值取决于 传入进来的 onResolved 回调函数执行后的返回值
// 返回的 Promise 对象中的 PromiseState 的值取决于:
// 执行器中的回调函数是同步操作:PromiseState = 执行器中调用的回调函数 是 resolve(fulfilled) 还是 reject(rejected)
//
return new Promise((resolve, reject) => {
// 封装函数
function callback(type) {
// 这里的 try catch 是为了防止实例对象调用 then 方法中的两个回调 onResolved, onRejected 函数中抛出错误的时候可以继续执行代码,并捕获错误信息
try {
// 获取回调函数的执行结果
let result = type(self.PromiseResult)
// 判断
// 如果实例中调用 then 方法的返回值也是一个 Promise对象,这里的 result等于也就是一个 Promise 对象的实例
// 然后继续调用实例上的 then 方法执行对应的 resolve() 和 reject 就可以,这样就会重新去执行 Promise 构造函数中的
// resolve 或者 reject 方法
if (result instanceof Promise) {
// 如果是Promise 类型的对象
result.then(v => {
resolve(v)
}, r => {
reject(r)
})
} else {
// 结果的对象状态为 [成功]
resolve(result)
}
} catch (e) {
reject(e)
}
}
// 调用回调函数,先判断 PromiseState 状态
// 这里是同步的状态下,已经修改过状态,并修改了结果以后,执行到 then 的操作
// 所以要先判断状态
if (this.PromiseState === 'fulfilled') {
// 这里要用 setTimeout 包裹一下,使其变为异步任务,把任务放进队列
setTimeout(() => {
callback(onResolved)
})
}
if (this.PromiseState === 'rejected') {
// 这里要用 setTimeout 包裹一下,使其变为异步任务,把任务放进队列
setTimeout(() => {
callback(onRejected)
})
}
// 这里是异步执行的状态下,状态还是 pending 没有被修改过,说明异步执行还在执行过程中,
// 然后先执行到了 then 方法。这里需要先把 onResolved, onRejected 两个回调函数先存一下
// 等到异步执行完成,调用了 resolve || reject 的时候,在 resolve || reject 函数中再去判断
// callback 对象中是否有存储回调函数,有的话在执行回调函数
if (this.PromiseState === 'pending') {
this.callbacks.push({
onResolved: function () {
callback(onResolved)
},
onRejected: function () {
callback(onRejected)
}
})
}
})
}
// 添加 catch 方法
catch (onRejected) {
return this.then(undefined, onRejected)
}
// 添加 resolve 方法
static resolve (value) {
// 返回 promise 对象
return new Promise((resolve, reject) => {
// 如果传入的 value 也是promise 实例, 则执行这个实例的 then 方法,然后执行对应的 resolve reject 方法
if (value instanceof Promise) {
value.then(v => {
resolve(v)
}, r => {
reject(r)
})
} else {
// 如果不是 promise 对象,直接设置成功
resolve(value)
}
})
}
// 添加 reject 方法
static reject (reason) {
return new Promise((resolve, reject) => {
reject(reason)
})
}
// 添加 all 方法
static all (promises) {
// 返回结果是 promise 对象
return new Promise((resolve, reject) => {
// 声明变量
let count = 0
let arr = []
// 遍历
for (let i = 0; i < promises.length; i++) {
//
promises[i].then(v => {
// 得知对象的状态是成功 每个 promise 对象都成功
count++
// 将当前 promise 对象成功的结果,存入到数组中,这里最好不好使用 push 添加,这样顺序有可能会乱,要使用下标的方式添加
arr[i] = v
// 确保 all 中所有的 Promise 对象都执行成功了,再去改变 promiseState 状态,再赋值
if (count === promises.length) {
resolve(arr)
}
}, r => {
reject(r)
})
}
})
}
// 添加 race 方法
static race (promises) {
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
promises[i].then(v => {
// 修改返回对象的状态为 [成功]
resolve(v)
}, r => {
// 修改返回对象的状态为 [失败]
reject(r)
})
}
})
}
}