为何需要 promise ?
在 promise 出现之前,异步编程由回调函数完成,很容易出现回调嵌套过多,也即常提到的“回调地狱”。回调地狱不仅是可读性差,维护起来也相当麻烦,如果某个环节出错了,经常无法准确定位问题。
Promise 正是为了解决这些问题而出现,链式调用解决了回调地狱的问题,它的错误传播机制实现了统一的错误信息处理。可以看一个 MDN 上的例子:
// 回调地狱
doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doThirdThing(newResult, function(finalResult) {
console.log('Got the final result: ' + finalResult);
}, failureCallback);
}, failureCallback);
}, failureCallback);
// Promise 方案
doSomething()
.then(function(result) {
return doSomethingElse(result);
})
.then(function(newResult) {
return doThirdThing(newResult);
})
.then(function(finalResult) {
console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);
动手实现
由于 ES6 中的 Promise 遵从 Promise A+ 规范,接下来就根据其主要的几个规则,开始一起动手实现吧!
我们先根据 Promise
的调用方式搭建它的构造函数:
class Promise {
constructor (excutor) {
this.value = null
this.reason = null
const resolve = (value) => {
this.value = value
}
const reject = (reason) => {
this.reason = reason
}
try {
excutor(resolve, reject)
} catch (reason) {
reject(reason)
}
}
}
增加状态机
每一个 Promise 实例只能有三个状态:pending
,fulfilled
,rejected
,且状态之间的转换只能是 pending => fulfilled
或 pending => rejected
。我们接着实现这个功能:
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class Promise {
constructor (excutor) {
this.value = null
this.reason = null
this.state = PENDING
const resolve = (value) => {
if (this.state === PENDING) {
this.value = value
this.state = FULFILLED
}
}
const reject = (reason) => {
if (this.state === PENDING) {
this.reason = reason
this.state = REJECTED
}
}
try {
excutor(resolve, reject)
} catch (reason) {
reject(reason)
}
}
}
实现 then
方法
Promise
中的 then
方法接受两个函数作为参数,分别是 Promise
成功和失败的回调。所以在 then
方法中,先判断 Promise
的状态,如果是成功,就将 this.value
传入回调并执行;如果失败,将 this.reason
传入回调并执行(所以需要全局的 value
和 reason
)。
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class Promise {
constructor (excutor) {
...
}
then (onFulfilled, onRejected) {
if (this.state === FULFILLED) {
onFullfilled(this.value)
} else {
onRejected(this.reason)
}
}
}
实现异步调用
目前我们的实现只是同步的,思考一下下面例子中 then
中的回调函数会不会执行?
let p1 = new Promise((resolve, reject) => {
setTimeout(resolve, 1000)
})
// 回调会执行吗?
p1.then((v) => {
console.log('success')
})
例子中的 resolve
函数是异步执行的,而 then
是同步执行的,也就是说 then
会先于 resolve
执行,那 then
执行的时候 Promise
的状态还是 Pending
。所以,then
中的回调函数是不会执行的。接下来我们做一些修改来解决这个问题:
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class Promise {
constructor (excutor) {
this.value = null
this.reason = null
this.state = PENDING
this.onFulfilledCallbacks = []
this.onRejectedCallbacks = []
const resolve = (value) => {
if (this.state === PENDING) {
this.value = value
this.state = FULFILLED
this.onFulfilledCallbacks.forEach(onFulfilledCallback => {
onFulfilledCallback()
})
}
}
const reject = (reason) => {
if (this.state === PENDING) {
this.reason = reason
this.state = REJECTED
this.onRejectedCallbacks.forEach(onRejectedCallback => {
onRejectedCallback()
})
}
}
try {
excutor(resolve, reject)
} catch (reason) {
reject(reason)
}
}
then (onFulfilled, onRejected) {
if (this.state === FULFILLED) {
this.onFulfilledCallbacks.push(() => {
onFulfilled(this.value)
})
} else {
this.onRejectedCallbacks.push(() => { onRejected(this.reason)
})
}
}
}
实现链式调用
在 Promise A+ 规范中,then
函数是可以链式调用的,也就是说 then
函数的返回值也是一个 Promise
,且这个 Promise
的 resolve
值是上一个 Promise
的 onFulfilled
函数的返回值,或 onRejected
函数的返回值。而如果上一个 Promise
的执行过程中发生错误,那么这个错误将被作为返回的 Promise
的 ``onRejected 函数的参数传入。
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class Promise {
...
resolvePromise (promise, x, resolve, reject) {
if (promise === x) {
reject(new TypeError('Chaining cycle'))
return
}
if (x instanceof APromise) {
if (x.state === PENDING) {
x.onFulfilledCallbacks.push(() => {
resolve(x.value)
})
} else if (x.state === FULFILLED) {
resolve(x.value)
} else if (x.state === REJECTED) {
reject(x.reason)
}
} else if (x && typeof x === 'object' || typeof x === 'function') {
let used
try {
let then = x.then
if (typeof then === 'function') {
then.call(x, y => {
if (used) return
used = true
this.resolvePromise(promise, y, resolve, reject)
}, r => {
if (used) return
used = true
reject(r)
})
} else {
if (used) return
used = true
resolve(x)
}
} catch (reason) {
if (used) return
used = true
reject(reason)
}
} else {
resolve(x)
}
}
then (onFulfilled, onRejected) {
const promise2 = new Promise((resolve, reject) => {
if (this.state === PENDING) {
this.onFulfilledCallbacks.push(() => {
try {
let x = onFulfilled(this.value)
this.resolvePromise(promise2, x, resolve, reject)
} catch (reason) {
reject(reason)
}
})
this.onRejectedCallbacks.push(() => {
if (typeof onRejected !== 'function') {
reject(this.reason)
} else {
try {
let x = onRejected(this.reason)
this.resolvePromise(promise2, x, resolve, reject)
} catch (reason) {
reject(reason)
}
}
})
} else if (this.state === FULFILLED) {
try {
let x = onFulfilled(this.value)
this.resolvePromise(promise2, x, resolve, reject)
} catch (reason) {
reject(reason)
}
} else {
try {
let x = onRejected(this.reason)
this.resolvePromise(promise2, x, resolve, reject)
} catch (reason) {
reject(reason)
}
}
})
return promise2
}
}
我们已经知道 then
方法会返回一个 Promise
对象,且这个对象的 resolve
值是前一个 Promise
的 onFulfilled
函数的返回值,或 onRejected
函数的返回值。但是当返回值是 thenable
对象,或是 Promise
时,需要有特殊处理。具体的逻辑处理在 resolvePromise(promise, x, resolve, reject)
方法中,我们接下来结合规范来看它的执行过程 :
- 如果
promise
和x
是同一个对象,那抛出一个Chaining cycle
的TypeError
- 如果
x
是一个Promise
对象,那么promise
将使用x
的状态x
处于PENDING
状态,promise
也将保持PENDING
状态直到x
被resolve
或reject
x
处于FULFILLED
状态,用x
的value
来resolve promise
x
处于REJECTED
状态,用同样的reason
来reject promise
- 如果
x
是一个对象或函数,将then
指向x.then
- 如果
then
是一个函数,那调用它,并将x
作为它的this
,以resolvePromise
作为第一个参数,rejectPromise
作为第二个参数,且:- 如果
resolvePromise
以一个y
值被调用了,则执行resolvePromise(promise, x, resolve, reject)
- 如果
resolvePromise
以一个r
值被拒绝了,则执行reject(r)
- 如果
resolvePromise
和rejectPromise
都被调用了,或者某个已被被多次调用了,则首次发生的调用生效,其余所有调用都被忽略。 - 如果在
then
调用过程中发生了错误,如果resolvePromise
和rejectPromise
都没被调用过,则reject
这个错误;否则忽略这个错误
- 如果
- 如果
then
不是函数,则以x
来resolve promise
- 如果
- 上述过程中,如果发生异常,用异常来
reject promise
实现 catch
的异常处理
考虑这个场景:promise
被 reject
且后续的 then
方法没有传入 onRejected
处理函数,那么根据 then
中的处理逻辑,第二个 promise
的 reason
就是前一个promise
的 reason
值。所以 throw
方法中只要调用 this.then(null, onRejected)
就能处理第一个 promise
未被处理的 reject
。
class Promise {
...
throw (onRejected) {
return this.then(null, onRejected)
}
}