从面试角度出发,可能我们会经常面临这几个问题:
- Promise解决了什么问题?
- Promise的业界实现都有哪些?
- Promise常用的API有哪些?
- 能不能手写一个符合规范的Promise?
- Promise在事件循环中的执行过程是怎样的?
- Promise有什么缺陷?能否解决?
1. Promise出现的原因
在Promise出现前,传统的异步编程中,处理异步请求时需要通过回调函数来完成,如果异步请求依赖其他异步请求,则代码需要通过层层嵌套会调的方式满足这种依赖,如果嵌套层数过多,可读性和可维护性都会变得很差,这时就会产生所谓的「回调地狱」,产生回调地狱的原因有两点:
- 嵌套调用:第一个函数的输出往往是第二个函数的输入
- 并发地处理多个异步请求
2.回顾Promise的使用方式
const p1 = new Promise((resolve, reject) => {
console.log('create a promise')
resolve('成功了')
})
console.log('after new promise')
const p2 = p1.then(data => {
console.log(data)
throw new Error('失败了')
})
const p3 = p2.then(data => {
console.log('success', data)
}, err => {
console.log('faild', err)
})
// create a promise
// after new promise
// 成功了
// faild Error: 失败了
- 首先在新建Promise对象时,需要传入一个成功的回调函数和一个失败的回调函数,Promise的主要业务流程都在这里执行。
- Promise的状态不可逆且唯一。
3.勾勒初级的Promise
通过回顾Promise的使用,可知Promise是一个构造函数,这个构造函数可以构造Promise实例。Promise只传一个参数,按照Promise/A+规范的命名, 这个参数叫做“executor”。executor会提供给构造者两个方法,一个是resolve,一个是reject。
简单剖析一下Promise的实质:Promise构造函数返回一个Promise对象实例,这个返回的Promise对象有一个then方法。在then方法中,调用者可以定义两个函数类型的参数,分别是onfulfilled和onrejected。其中onfulfilled通过参数可以获取Promise对象经过resolve处理过的值,onrejected可以获取Promise对象经过reject处理过的值。
在已有Promise构造函数的基础上加上then原型方法的骨架:
function Promise(executor) {
}
Promise.prototype.then = function(onfulfilled, onrejected) {
}
下面先看一个示例,从示例中理解Promise的重点内容:
let promise1 = new Promise((resolve, reject) => {
resolve('data')
})
promise1.then(data => {
console.log(data)
})
let promise2 = new Promise((resolve, reject) => {
reject('error')
})
promise2.then(data => {
console.log(data)
},error => {
console.log(error.)
})
在使用new关键字调用Promise时,在合适的时机(往往是异步操作结束时)调用executor的参数resolve,并将经过resolve处理后的值作为resolve的函数参数执行,这个值可以在then方法的第一个参数(onfulfilled)中拿到;同理reject中的参数可以在onrejcted中拿到。
因此在实现Promise时,需要有两个变量来存储经过resolve处理过的值以及经过reject处理过的值。同时还需要存在一个状态,这个状态就是Promise实例的状态(pending, fulfilled, rejected)。最后需要提供resolve以及reject方法,这两个方法需要作为executor的参数提供给开发者使用。
function Promise(executor) {
this.status = 'pending'
this.value = null
this.reason = null
const resolve = (value) => {
if (this.status === 'pending') {
this.value = value
this.status = 'fulfilled'
}
}
const reject = (value) => {
if (this.status === 'pending') {
this.value = value
this.status = 'rejected'
}
}
executor(resolve, reject)
}
Promise.prototype.then = function (onfulfilled, onrejected) {
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error}
if(this.status === 'fulfilled') {
onfulfilled(this.value)
}
if(this.status === 'rejected') {
onrejected(this.reason)
}
}
为保证onfulfilled、onrejected能够健壮地执行,我们为其设置了默认值,其默认值为一个函数元。
then放在Promise的构造函数的原型上是因为每个Promise实例的then方法逻辑都是一样的,实例在调用该方法时,可以通过原型来调用,而不需要每次实例化都新建一个then方法,以便节省内存。
4.Promise异步实现完善
通过下面示例的代码来分析目前我们的实现差了哪些内容:
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('data')
}, 2000)
})
promise.then(data => {
console.log(data)
})
正常来讲,上述代码应该在2s后输出data,但现在代码没有输出任何信息,因为上面已实现的逻辑全是同步的。上述代码在setTimeout中调用resolve,也就是在2秒后才会执行resolve方法,更改Promise实例的状态。
所以我们应该在什么时间去调用onfulfilled方法?
我们应该在合适的时间去调用onfulfilled方法,这个合适的时间应该是开发者调用resolve的时刻,我们可以先在状态为pending时把开发者传进来的onfulfilled方法存起来,再在resolve方法中执行即可。
function Promise(executor) {
this.status = 'pending'
this.value = null
this.reason = null
this.onFulfilledFunc = Function.prototype
this.onRejectedFunc = Function.prototype
const resolve = (value) => {
if (this.status === 'pending') {
this.value = value
this.status = 'fulfilled'
this.onFulfilledFunc(this.value)
}
}
const reject = (value) => {
if (this.status === 'pending') {
this.value = value
this.status = 'rejected'
this.onRejectedFunc(this.reason)
}
}
executor(resolve, reject)
}
Promise.prototype.then = function (onfulfilled, onrejected) {
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error}
if(this.status === 'fulfilled') {
onfulfilled(this.value)
}
if(this.status === 'rejected') {
onrejected(this.reason)
}
if(this.status === 'pending') {
this.onFulfilledFunc = onfulfilled
this.onRejectedFunc = onrejected
}
}
通过测试发现,上述Promise实现了异步,接着看下面的一个例子,判断以下代码的输出结果:
let promise = new Promise((resolve, reject) => {
resolve('data')
})
promise.then(data => {
console.log(data)
})
console.log(1)
正常顺序是先输出1,再输出data。
而目前实现的Promise代码没有考虑这种情况,因此需要将resolve和reject的执行放到任务队列中。暂时先放到setTimeout中,保证异步执行。
const resolve = (value) => {
if (value instanceof Promise) {
return value.then(resolve, reject)
}
setTimeout(() => {
if (this.status === 'pending') {
this.value = value
this.status = 'fulfilled'
this.onFulfilledFunc(this.value)
}
})
}
const reject = (value) => {
setTimeout(() => {
if (this.status === 'pending') {
this.value = value
this.status = 'rejected'
this.onRejectedFunc(this.reason)
}
})
}
executor(resolve, reject)
5.Promise细节完善
5.1在Promise实例状态变更前添加多个then方法,比如:
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('data')
}, 2000)
})
promise.then(data => {
console.log(`1: ${data}`)
})
promise.then(data => {
console.log(`2: ${data}`)
})
// 以上代码应该输出:
// 1:data
// 2:data
而我们的实现只会输出2:data,这是因为第二个then中的onFulfilledFunc会覆盖第一个then方法中的onFulfilledFunc。这个问题可以通过将所有then方法中的onFulfilledFunc储存到一个数组里,在当前Promise被决议时依次执行onFulfilledArray数组内的方法即可。对于onRejectedFunc同理。
function Promise(executor) {
this.status = 'pending'
this.value = null
this.reason = null
this.onFulfilledArray = []
this.onRejectedArray = []
const resolve = (value) => {
if (value instanceof Promise) {
return value.then(resolve, reject)
}
setTimeout(() => {
if (this.status === 'pending') {
this.value = value
this.status = 'fulfilled'
this.onFulfilledArray.forEach(func => {
func(value)
})
}
})
}
const reject = (value) => {
setTimeout(() => {
if (this.status === 'pending') {
this.value = value
this.status = 'rejected'
this.onRejectedArray.forEach(func => {
func(value)
})
}
})
}
executor(resolve, reject)
}
Promise.prototype.then = function (onfulfilled, onrejected) {
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error}
if(this.status === 'fulfilled') {
onfulfilled(this.value)
}
if(this.status === 'rejected') {
onrejected(this.reason)
}
if(this.status === 'pending') {
this.onFulfilledArray.push(onfulfilled)
this.onRejectedArray.push(onrejected)
}
}
5.2处理构造函数出错的情况
在构造函数中如果出错,将自动触发Promise实例状态变为reject,因此可以用时try...catch对executor进行包裹。
try{
executor(resolve, reject)
} catch(e) {
reject(e)
}
通过以上的实现,可以对Promise总结出一些结论:
- Promise的状态具有凝固性
- Promise可以在Then方法第二个参数重进行错误处理
- Promise实例可以添加多个then处理场景
6. 实现Promise的链式调用
通过两个例子来观察Promise的链式调用:
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('lucas')
}, 2000)
})
promise.then(data => {
console.log(data)
return `${data} next then`
}).then(data => {
console.log(data)
})
// 2s后输出lucas
// 紧接着输出 lucas next then
如果在第一个then方法体中的onfulfilled函数中返回另一个Promise实例,那会是什么结果?
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('lucas')
}, 2000)
})
promise.then(data => {
console.log(data)
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(`${data} next then`)
}, 4000)
})
}).then(data => {
console.log(data)
})
// 2s后输出lucas
// 4s后输出 lucas next then
由此可知,一个Promise实例then方法的onfulfilled函数和onrejected函数是支持再次返回一个Promise实例的,也支持返回一个非Promise实例的普通值。并且返回的这个Promise实例或普通值会传给下一个then方法的onfulfilled函数或onrejected函数。
6.1 链式调用的初步实现
首先分析,为了支持then方法的链式调用,每一个then方法的onfulfilled函数和onrejected函数是不是都应该返回一个Promise实例?
Promise.prototype.then = function (onfulfilled, onrejected) {
let promise2
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error}
if(this.status === 'fulfilled') {
return promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try{
// 将onfulfilled处理后的结果赋给新的promise2的resolve
let result = onfulfilled(this.value)
resolve(result)
} catch(e) {
reject(e)
}
})
})
}
if(this.status === 'rejected') {
return promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try{
// 将onrejected处理后的结果赋给新的promise2的resolve
let result = onrejected(this.value)
resolve(result)
} catch(e) {
reject(e)
}
})
})
}
if(this.status === 'pending') {
return promise2 = new Promise((resolve, reject) => {
this.onFulfilledArray.push(() => {
try{
let result = onfulfilled(this.value)
resolve(result)
} catch(e) {
reject(e)
}
})
this.onRejectedArray.push(() => {
try{
let result = onrejected(this.reason)
resolve(result)
} catch(e) {
reject(e)
}
})
})
}
}
这里要重点理解this.status === 'pending'判断分支里的逻辑。之前是将onfulfilled/onrejected存储在数组中,等异步结束后依次执行数组里的函数;现在是将一个新的函数存在数组中,等异步结束后一次执行数组里的函数。
以上代码只实现了onfulfilled/onrejected中显式返回一个普通值的链式调用,之后再完善一下显式返回Promise实例的链式调用。
6.2 完善显式返回Promise实例的链式调用
基于第一种onfulfilled和onrejected函数返回一个普通值的情况,要实现这种onfulfilled函数和onrejected函数返回一个Promise实例的情况并不困难。但是需要将变量reuslt满足既可以是一个普通值,也可以是一个Promise实例。我们可以抽象出resolvePromise方法进行统一处理。
const resolvePromise = (promise2, result, resolve, reject) => {}
现在的任务就是完成resolvePromise函数,这个函数接受的参数如下:
- promise2: 返回的Promise实例。
- result: onfulfilled或onrejected函数返回的实例。
- resolve: promise2的resolve方法。
- reject: promise2的reject方法。
const resolvePromise = (promise2, result, resolve, reject) => {
// 当result和promise2相等时,可能出现死循环,为了避免,在这里会执行reject
if(result === promise2) {
reject(new TypeError('error due to circular refrence'))
}
// 是否已经执行过onfulfilled或onrejected
let consumed = false
let thenable
// 如果返回的是Promise,则调用resolvePromise去包装返回的promise2
if (result instanceof Promise) {
if(result.status === 'pending') {
result.then(function(data) {
resolvePromise(promise2, data, resolve, reject)
}, reject)
} else {
result.then(resolve, reject)
}
return
}
let isComplexResult = target => (typeof target === 'function' || typeof target === 'object') && (target !== null)
// 如果返回的是疑似Promise类型
if(isComplexResult(result)) {
try{
thenable = result.then
// 判断返回的是疑似Promise类型
if(typeof thenable === 'function') {
thenable.call(result, function(data) {
if(consumed) {
return
}
consumed = true
return resolvePromise(promise2, data, resolve, reject)
}, function(error) {
if(consumed) {
return
}
consumed = true
return reject(error)
})
}
else {
resolve(result)
}
} catch(e) {
if(consumed) {
return
}
consumed = true
return reject(e)
}
}
else {
resolve(result)
}
}
如果reuslt不是Promise实例,不是对象,也不是函数,而是一个普通值的话,则可以直接对promise2进行决议。
对于onfulfilled函数返回的结果result:如果result含有then属性方法,那么我们称该属性方法为thenable,说明result是一个Promise实例,当执行该实例的then方法时,返回结果可能是一个Promise实例类型,也可能是一个普通值,因此还要递归resolvePromise。
以下是完整代码:
function Promise(executor) {
this.status = 'pending'
this.value = null
this.reason = null
this.onFulfilledArray = []
this.onRejectedArray = []
const resolve = (value) => {
if (value instanceof Promise) {
return value.then(resolve, reject)
}
setTimeout(() => {
if (this.status === 'pending') {
this.value = value
this.status = 'fulfilled'
this.onFulfilledArray.forEach(func => {
func(value)
})
}
})
}
const reject = (value) => {
setTimeout(() => {
if (this.status === 'pending') {
this.value = value
this.status = 'rejected'
this.onRejectedArray.forEach(func => {
func(value)
})
}
})
}
executor(resolve, reject)
}
Promise.prototype.then = function (onfulfilled, onrejected) {
let promise2
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error}
if(this.status === 'fulfilled') {
return promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try{
// 将onfulfilled处理后的结果赋给新的promise2的resolve
let result = onfulfilled(this.value)
resolvePromise(promise2, result, resolve, reject)
} catch(e) {
reject(e)
}
})
})
}
if(this.status === 'rejected') {
return promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try{
// 将onrejected处理后的结果赋给新的promise2的resolve
let result = onrejected(this.value)
resolvePromise(promise2, result, resolve, reject)
} catch(e) {
reject(e)
}
})
})
}
if(this.status === 'pending') {
return promise2 = new Promise((resolve, reject) => {
this.onFulfilledArray.push(() => {
try{
let result = onfulfilled(this.value)
resolvePromise(promise2, result, resolve, reject)
} catch(e) {
reject(e)
}
})
this.onRejectedArray.push(() => {
try{
let result = onrejected(this.reason)
resolve(result)
} catch(e) {
reject(e)
}
})
})
}
}
7.Promise穿透实现
此时的Promise基本已经实现了95%,不是100%是因为有一处细节没有完成。
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('lucas')
},2000)
})
promise.then(null)
.then(data => {
console.log(data)
})
这段代码会在2s后输出lucas。这就是Promise穿透现象:给then()函数传递非函数值为其参数时,实际上会被解析成then(null),这时,上一个Promise对象的决议结果便会被“穿透”到下一个then方法中。
如何实现Promise穿透呢?
其实上面的代码已经实现了。
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error}
如果onfulfilled不是函数,则给一个默认值,该默认值是返回其参数的函数。