Open gogoend opened 4 years ago
来看看Promise到底是什么以及怎么用一文中,笔者对Promise的相关用法进行了概述。
类方法包括:
实例方法包括:
此外实例上还包括了几个不能够直接被访问的属性:
当然,以上API要想全实现恐怕并不是一步登天的事情。首先,我们来实现一个简单的、可以通过Promise A+规范测试的Promise吧。
要想实现一个可以通过 promises-aplus-tests 全部用例的 Promise,我们需要实现以下内容:
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
这里定义了Promise的执行过程的三种状态 ——
const Promise = function (executor) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
this.onFulfilledCbs = [];
this.onRejectedCbs = [];
const resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
this.onFulfilledCbs.forEach(func => {
func()
})
}
}
const reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
this.onRejectedCbs.forEach(func => {
func()
})
}
}
try {
executor(resolve, reject)
} catch (err) {
reject(err)
}
}
这是Promise类的定义。 类中包含这些属性:
值得注意的是, 根据Promise A+ 规范(下列简称“根据规范”)2.1,一旦实例的状态由 pending
转变为其他值,实例的状态便不能够再次发生改变。
同样这里还有 resolve 和 reject 两个函数:
接下来在将会立即执行 Promise 实例在构造时所传入的函数(又被称为“执行器”)。
// 注释1 - gogoend
Promise.prototype.then = function (fulfilledCb, rejectedCb) {
// 注释2 - gogoend
fulfilledCb = typeof fulfilledCb === 'function' ? fulfilledCb : val => val;
rejectedCb = typeof rejectedCb === 'function' ? rejectedCb : reason => { throw reason }
// 注释3 - gogoend
let promise2 = new Promise((resolve, reject) => {
// 注释4 - gogoend
if (this.status === PENDING) {
this.onFulfilledCbs.push(() => {
setTimeout(() => {
try {
let x = fulfilledCb(this.value)
// 注释5 - gogoend
resolvePromise(promise2, x, resolve, reject)
} catch (err) {
reject(err)
}
})
})
this.onRejectedCbs.push(() => {
setTimeout(() => {
try {
let x = rejectedCb(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (err) {
reject(err)
}
})
})
}
// 注释6 - gogoend
if (this.status === FULFILLED) {
setTimeout(() => {
try {
let x = fulfilledCb(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (err) {
reject(err)
}
})
}
if (this.status === REJECTED) {
setTimeout(() => {
try {
let x = rejectedCb(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (err) {
reject(err)
}
})
}
})
return promise2
}
代码中的注释:
then函数可接受两个参数,这两个参数的类型都是函数。分别表示前一Promise实例被 resolve 、被 reject 后将会执行的函数。这两个函数分别接受Promise执行成功的结果、执行失败的原因来作为参数。
根据规范2.2.1,这两个参数为可选参数,若两者非函数,则忽略;这里直接返回值或抛出错误。
规范2.2.7指出,then函数需要返回一个Promise实例,所以接下来所有操作都在将要返回的实例中进行。
每当执行then的时候,都要判断一次当前Promise是何种状态。若Promise当前状态是pending
,则表示Promise实例还未被接受或是拒绝,then中的函数则将会被放入到数组中等待执行;
此处有一个 resolvePromise
函数,该函数用于对Promise各种结果的值进行不同判断,从而进行不同操作。详见下文
若Promise当前状态不再是pending
,则表示Promise实例已被接受或拒绝,then中的函数将会立即执行。根据规范2.2.4以及3.1,fulfilledCb、rejectedCb函数需要被异步执行,因此外面使用了setTimeout进行包裹。
function resolvePromise(promise2, x, resolve, reject) {
// 注释1 - gogoend
if (x === promise2) {
reject(new TypeError('循环引用'))
}
// 注释2 - gogoend
if ((x !== null && typeof x === 'object') || typeof x === 'function') {
// 注释3 - gogoend
let called = false
// 注释4 - gogoend
try {
let then = x.then
if (typeof then === 'function') {
then.call(
x,
y => {
if (called) return
called = true
resolvePromise(promise2, y, resolve, reject)
},
reason => {
if (called) return
called = true
reject(reason)
}
)
} else {
if (called) return
called = true
resolve(x)
}
} catch (err) {
if (called) return
called = true
reject(err)
}
} else {
resolve(x)
}
}
本函数主要用于判断上一步的Promise中所得到的结果是什么,从而进行对应操作。 注释:
如果上一步返回的结果x
与上一步的Promise是同一个值,则表示Promise发生了循环调用。形如下方代码:
let thePromise = new Promise((resolve,reject)=>{
resolve();
}).then(
()=> thePromise
)
这种情况下,Promise实例则将会一直在等待自己完成,状态永远不会转变为 pending
之外的状态。
此处用于判断上一步结果x
是什么类型的值。如果x
是一个对象或者函数,且x
中包含 then
方法,则意味着 x
具有 Promise
的一些特性,也就是thenable
;此时将会调用then
方法,调用时then
的this
指向x
,在then
中的fulfilledCb
参数中传入新的结果y
, 并将y
传入再次调用的resolvePromise
进行递归调用(调用时传入promise2、新的执行结果y
、resolve函数、reject函数),这就实现链式调用,直到最终结果不再thenable
;如果 x
是一个普通的、无 Promise 特征的值,则表示 x
已经是最终结果了,此时就执行 resolve(x)
。
called - 成功/失败后不允许再让Promise发生状态改变。
为何此处需要使用 try……catch……? 如下示例:
Object.defineProperty(window,'then',{get(){throw new Error()}})
虽然该用例很极端,然而为了代码健壮性也值得考虑考虑。
链式调用的核心,即解析函数
解析函数大致类型定义:
function resolvePromiseChain ( currentPromise: Promise<any>, // 当前要被解析的Promise currentResult: any, // 当前Promise对应的结果 - 可能是最终结果,也可能需要继续解析 resolve, // 当前Promise中的resolve函数 reject // 当前Promise的reject函数 ) => any
这里相当于在当前Promise加了一个回调来处理一些内部状态,递归调用解析函数,并返回其结果。形如:
currentPromise.then( (nextResult) => {...}, // onResolve (reason) => {...} // onReject )
- 如果resolve被调用,参数为nextResult,则在onResolve回调中执行解析函数(递归地resolve currentPromise):
resolvePromiseChain(currentPromise, nextResult, resolve, reject)
- 如果reject被调用,参数为reason,则在onReject回调中reject currentPromise
- 如果resolve与reject同时被调用,则以第一次调用的为准
- 如果调用then时抛出了错误,
- 如果onResolve或onReject已被调用,则忽略这一错误
- 否则reject currentPromise
Promise犹如一个信使,无论是好消息或是坏消息,只要有了消息,就会告诉每一个想要知道消息的人。
回顾一下曾经在来看看Promise到底是什么以及怎么用立过的一个 Flag —— 手写一个 Promise。。。怎么写呢?这是个问题。