一、Promise 的用法
要想实现一个 Promise / A+ 规范的 Promise,首先要弄清楚如何使用 Promise。 Promise()
构造函数接收一个回调函数:executor
,其中 executor
是一个双参函数,包含 resolve 和 reject
,用于返回一个新的 Promise 对象,并立即执行 executor
。
Promise的实现会立即执行 executor
,并传入resolve
和 reject
函数(Promise
构造器将会在返回新对象之前executor
)。
当resolve
和reject
函数被调用时,它们分别对 promise 执行resolve
和reject
。executor
通常会触发一些异步运算,一旦运算成功完成,则resolve
掉这个promise,如果出错则reject掉。如果executor
函数执行时抛出异常,promise状态会变为rejected
。executor
的返回值也会被忽略。
new Promise((resolve, reject) => {
// do something
try {
resolve(value)
} catch (reason) {
reject(reason)
}
}).then(value=> {
// do something
}).catch(reason => {
// do something
})
Promise 有以下几个静态方法:
- Promise.all(iterable):返回一个新的 promise 对象,该promise对象在 iterable 参数对象里所有的 promise 对象都成功的时候才会触发成功,一旦有任何一个 iterable 里面的 promise 对象失败则立即触发该 promise 对象的失败。
- Promise.allSettled(interable):等到所有 interable 的 Promise 都被敲定(settled),即状态为 fulfilled 或 rejected 时,返回一个新的 Promise。该 Promise 的 value 是一个数组,用于存储所有 Promise 的执行结果。
- Promise.any(interable):有一个 Promise 成功,就返回那个成功的值。
- Promise.race(interable):任意一个 Promise 先成功或失败时,返回这个 Promise
- Promise.resolve(value)
- Promise.reject(reason)
二、Promise 的特点(Promise / A+ 规范)
1、Promise 的三种状态
根据 Promise / A+ 规范 2.1 章节的描述,Promise 作为一个状态机,他有三种状态,分别是 pending (等待态),fulfilled(成功态),rejected(失败态)
- 当 Promise 处于 pending 状态时:
- Promise 可以转化为 fulfilled 状态和 rejected 状态
- 当 Promise 处于 fulfilled 状态时:
- Promise 不可以转换成别的状态
- Promise 必须有一个不可变的返回值:value
当 Promise 处于 rejected 状态时:
- Promise 不可以转换成别的状态
Promise 必须有一个不可变的返回原因:reason
class Promise {
constructor(executor) {
this.state = 'pending'
this.value = void 0
this.reason = void 0
const resolve = value => {
if(this.state === 'pending') {
this.state = 'fulfilled'
this.value = value
}
}
const reject = reason => {
if(this.state === 'pending') {
this.state = 'rejected'
this.reason = reason
}
}
try {
executor(resolve, reject)
} catch(e) {
reject(e)
}
}
}
2、then 方法
Promise 除了有 pending、fulfilled、rejected 这三种状态的特点外,还有一个特点是,Promise 是 thenable 的,即 then 方法可以被链式调用。
const p = new Promese()
p.then()
p.then()
并且 Promise 必须提供一个 then 方法访问当前或者最终成功的结果或者失败原因。
根据 Promise / A+ 规范 的定义,then 方法有两个参数:onFulfilled 和 onRejected。按照规范描述,其特点如下:
onFulfilled
和onRejected
都为可选的参数时:- 如果
onFulfilled
不是函数,则它将被忽略。 - 如果
onRejected
不是函数,则它将被忽略。
- 如果
- 如果
onFulfilled
是函数:- 此函数在 promise成功后(fulfilled)被调用,并把
promise
的成功值(value)作为它的第一个参数。 - 在
promise
成功(fulfilled)之前一定不能提前被调用。 - 该函数只执行一次
- 此函数在 promise成功后(fulfilled)被调用,并把
- 如果
onRejected
是函数:- 此函数在
promise
失败(rejected)时被调用, 并且把promise
的失败原因(reason)当成第一个参数。 - 在
promise
失败(rejected)之前一定不能提前被调用。 - 该函数只执行一次。
- 此函数在
onFulfilled
和onRejected
只有在 执行上下文 堆栈仅包含平台代码时才可被调用onFulfilled
和onRejected
必须被作为函数调用 (即没有this值)。then
方法可以被同一个promise多次调用promise
成功时, 所有onFulfilled
回调函数需按照最原始的then顺序来调用。promise
失败时,所有各自的onRejected
回调都必须按照其对then的原始调用顺序执行。
- then必须返回一个promise:
promise2 = promise1.then(onFulfilled, onRejected)
- 如果
onFulfilled
或者onRejected
返回一个值 x,则运行下面的 Promise 解决过程:[[Resolve]](promise2, x)
- 如果onFulfilled或 onRejected抛出一个异常e,promise2 必须被拒绝(rejected)并把e当作失败的原因(reason)
- 如果
onFulfilled
不是一个函数且promise1成功执行(fulfilled),则promise2
将会接收promise1
传递下来的成功(fulfilled)的值 - 如果
onRejected
不是一个函数,并且promise1
已经失败了(rejected),则必须以同promise1
相同的失败(rejected)的原因(reason)传递到promise2
- 如果
官方的描述有点啰嗦,总结的思维导图如下:
为了搞懂上述逻辑,我们需要熟悉一下几个概念:
- promise2:因为 then 函数需要返回一个新的 Promise 对象,所以我们在 then 方法的实现里,实例化了一个 Promise对象 promise2
- x:then 方法的返回值,在 then 方法内及 onFulfilled 和 onRejected 的返回值
- resolvePromise:Promise 的处理方法。
Then 方法的主要逻辑如下:
- Then 方法有两个参数:onFulfilled 和 onRejected,并判断二者不是 function 的情况
- Then 方法返回值为一个 Promise 对象:Promise2
- 如果 Promise 处于 pending 态,则在异步队列中添加该异步方法
- 否则执行 onFulfilled 和 onRejected,并将 promise2 与返回值 x 做校验(resolvePromise)
根据上述描述,我们需要在 Promise 类上添加两个队列属性,分别用于存放成功与失败的执行函数
class Promise {
constructor(executor) {
this.state = 'pending'
this.value = void 0
this.reason = void 0
this.onFulfilledCallbacks = []
this.onRejectedCallbacks = []
const resolve = value => {
if(this.state === 'pending') {
this.state = 'fulfilled'
this.value = value
// 执行所有成功队列的方法
this.onFulfilledCallbacks.forEach(fn => fn())
}
}
const reject = reason => {
if(this.state === 'pending') {
this.state = 'fulfilled'
this.reason = reason
// 执行所有失败队列里的方法
this.onRejectedCallbacks.forEach(fn => fn())
}
}
try {
executor(resolve, reject)
} catch(e) {
reject(e)
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onFulfilled : reason => reason
const promise2 = new Promise((resolve, reject) => {
if(this.state === 'pending') {
// 将 onFulfilled 和 onRejected 添加到队列中
this.onFulfilledCallbacks.push(_ => {
const x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
})
this.onRejectedCallbacks.push(_ => {
const x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
})
}
if(this.state === 'fulfilled') {
const x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
}
if(this.state === 'rejected') {
const x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
}
})
return promise2
}
}
3、Promise 的处理过程
上述代码已经基本实现了一个 Promise,但是我们没有实现 resolvePromise 方法。Promise / A+ 规范详细的描述了 Promise 的处理过程,因此我们依据 Promise / A+ 规范中的 2.3 章节来实现 resolvePromise。具体规范如下:
Promise处理过程是一个抽象的操作,其需输入一个 promise 和一个值 x:
我们表示为[[Resolve]](promise, x)
。
如果x 是一个 thenable 对象,处理程序将以这个promise
对象的 then 返回值继续传递下去,如果x
是一个普通值,则以成功的回调传递给下去。
这种 thenable 的特性使得 Promise 的实现更具有通用性:只要其暴露出一个遵循 Promise/A+ 协议的 then 方法即可;这同时也使遵循 Promise/A+ 规范的代码可以与那些不太规范但可用的实现能良好兼容。
运行 [[Resolve]](promise, x)
, 需要遵循以下几个步骤:
- 如果
promise
和x
是相同的, 则报TypeError
错误。 - 如果
x
是一个 promise对象, 则使 promise 接受 x 的状态 [3.4]- 如果
x
处于等待状态,promise需保持为等待态直至 x 被执行或拒绝 - 如果
x
处于执行态,用相同的值执行promise
- 如果
x
处于拒绝态,用相同的据因拒绝promise
- 如果
- x是对象或者函数时:
- 把
x.then
赋值给then
方法 [3.5] - 如果在获取属性
x.then
的过程中导致抛出异常e,则拒绝promise并用e
作为拒绝原因。 - 如果
then
是函数,则将x作为函数的作用域的this被绑定并调用。传递两个回调函数作为参数,第一个参数叫做resolvePromise
,第二个参数叫做rejectPromise
:- 如果
resolvePromise
以值 y为参数被调用,则运行[[Resolve]](promise, y)
- 如果
rejectPromise
以据因r
为参数被调用,则以据因r
拒绝promise
- 如果
resolvePromise
和rejectPromise
均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用 - 如果调用then方法抛出了异常e:
- 如果
resolvePromise
或rejectPromise
已经被调用,则忽略它 - 否则以
e
为拒绝promise
的原因
- 如果
- 如果
- 如果
then
不是函数,以x为参数执行 promise
- 把
- 如果
x
不是对象或者函数,以x为参数执行 promise
如果 promise 被一个 thenable 解决,且该 thenable 参与一个 thenable 循环链,那么 [Resolve]的递归性质最终导致 [Resolve]被再次调用,上述算法将导致无限递归。支持实现此类递归检测,并以一个 TypeError类型的值作为原因拒绝 promise,但此类检测不是必须的。 [3.6]
function resolvePromise(promise2, x, resolve, reject) {
// 如果 x 和 promise2 相等,则 reject TypeError 错误
if (x === promise2) {
return reject(new TypeError('Chaining cycle detected for promise'))
}
// 如果 x 是 object 或 function
let called = false
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try {
const then = x.then
if (typeof then === 'function') {
then.call(x, y => {
if(called) return
called = true
resolvePromise(promise2, y, resolve, reject)
}, e => {
if (called) return
called = true
reject(e)
})
} else {
resolve(x)
}
} catch(e) {
if (called) return
called = true
reject(e)
}
} else {
// 如果 x 是 Promise,则 resolve
resolve(x)
}
}
三、校验实现的 Promise 是否符合 Promise / A+ 规范:
四、其他 Promise 静态成员的实现
1、Promise.resolve
Promise.resolve = function(value) {
return new Promise((resolve, reject) => {
resolve(value)
})
}
2、Promise.reject
Promise.reject = function(reason) {
return new Promise((resolve, reject) => {
reject(reason)
})
}
3、Promise.all
Promise.all = function(promises) {
const queue= []
let i = 0
cosnt processData = (index, data) => {
queue[index] = data
i++
if (i === queue.length) {
resolve(data)
}
}
return new Promise((resolve, reject) => {
for (let i=0; i<promises.length, i++) {
promises[i].then(data => {
process(i, data)
}, reject)
}
})
}
4、Promise.race
Promise.race = function(promises) {
return new Priomise((resolve, reject) => {
for(let i=0; i<promises.length, i++) {
promises[i].then(resolve, reject)
}
})
}
5、Promise.any
Promise.any = function(promises) {
return new Promise((resolve, reject) => {
let n = promises.length
const errs = []
if (n === 0) return reject(new AggregateError('All promises were rejected'))
for(let i=0; i<n; i++) {
promises[i].then(value => resolve(value), err => {
n--
errs.push(err)
if(n === 0) return reject(new AggregateError(err))
})
}
})
}
6、Promise.allSettled
Promise.allSettled = function(promises) {
return new Promsie((resolve, reject) => {
let n = promises.length
const data = []
for (let i=0; i<n; i++) {
promises[i].then(value => {
data[i] = { status: 'fulfilled', value }
}, reason => {
data[i] = { status: 'rejected', reason }
}).finally(_ => {
if(!--n) {
resolve(data)
}
})
}
})
}
五、最终代码
Promises/A+ (promisesaplus.com)
使用 Promise - JavaScript | MDN (mozilla.org)
Promise - JavaScript | MDN (mozilla.org)