Promise/A+规范是开源健全可互操作的JavaScript Promise 规范,通过阅读PromiseA+规范的文档来实现一个Promise将会事半功倍,有助于我们更好的理解Promise的核心思想。
Promise代表着异步操作的最终结果。与Promise进行交互的主要方式是通过then方法, 它的核心其实是then方法收集回调函数,然后通过reslove或者reject触发所有then收集的回调函数,是一个发布订阅模式。
PromiseA+规定了如下内容:
1.Promise 的相关术语
1.1 promise 是一个带有符合此规范的then方法的对象或者函数。
1.2 thenable 是一个定义了一个then方法的对象或者函数。
1.3 value 是一个任意合法的JavaScript值(包括undefined, thenable,或者promise)。
1.4 exception 是一个使用throw语句抛出的值。
1.5 reason 是一个指出为什么promise被rejected的原因。
2.Promise 的具体要求
2.1 Promise 的状态
一个 promise 必须是三种状态其中的一种状态:pending,fulfilled或者rejected。
2.1.1 当 promise 处于 pending 状态时:
2.1.1.1 可以转变到
fulfilled或者rejected状态。
2.1.2 当 promise 处于 fulfilled 状态时:
2.1.2.1 一定不能够转变到其他任何一种状态。
2.1.2.2 必须有一个value,并且这个值一定不能改变。
2.1.3 当promise处于rejected状态时:
2.1.3.1 一定不能够转变到其他任何一种状态。
2.1.3.2 必须有一个reason,并且这个值不能够改变。
在这里,一定不能改变 意味着不可变的身份(即===),但并不意味着深层不变性。
根据上面 2.1 promise 状态 状态描述信息可以得出以下信息:
- 每个
Promise都有三个状态,pennding等待态,resolve标识变成成功态fulfilled,reject标识变成失败态rejected。 - 一旦成功就不能失败 一旦失败不能成功。
- 当
Promise抛出异常后 也会走失败态。
根据上面2.1 promise 状态的要求,实现代码如下:
// 定义状态枚举
const STATUS = {
pennding: 'PENNDING',
fulfilled: "FULFILLED",
rejected: 'REJECTED'
}
class Promise{
constructor(executor) { // executor就是new Promise((resolve,reject)=>{}) 传入的(reslove,reject)=>{...}函数
this.status = STATUS.pennding // 当前的状态 创建时为pennding
this.value = null // 当前值
this.reason = null // 失败原因
// resolve 函数
const resolve = (value) => {
if (this.status === STATUS.pennding) {
this.status = STATUS.fulfilled // 将状态置为fulfilled
this.value = value
}
}
// reject 函数
const reject = (reason) => {
if (this.status === STATUS.pennding) {
this.status = STATUS.rejected // 将状态置为fulfilled
this.reason = reason
}
}
// 捕获异常 用于处理用户reslove/reject了抛出的错误,例如throw Error('xxxxx')
try {
executor(reslove, reject)
} catch (err) {
reject(err)
}
}
then(onFulfilled, onRejected) {
// 成功的回调
if (this.status === STATUS.fulfilled) { // 只有状态为fulfilled时才能执行onFulfilled方法,用户调用resolve时会将状态改为fulfilled
onFulfilled(this.value)
}
// 失败的回调
if (this.status === STATUS.rejected) { // 只有状态为rejected时才能执行onRejected方法,用户调用reject时会将状态改为rejected
onRejected(this.reason)
}
}
}
// case1
const promise = new Promise((resolve,reject)=>{
resolve('success')
reject('error')
})
promise.then(
value=> console.log(value),
reason=> console.log(reason),
)
// => 'success' 先resolve了状态发生了变化,所以后面的reject不会执行
这样就实现了一个最简单的,能then调用的Promise类。
但是还有很多问题,例如then里面如果是异步代码,resolve或reject就不会执行,因为那个时候状态还是pennding。
这个问题可以用发布订阅模式来解决,在状态是pennding的时候,每次then就把传入的回调收集起来,resolve或reject的时候,把收集的回调全部执行。
实现如下:
// 定义状态枚举
const STATUS = {
pennding: 'PENNDING',
fulfilled: "FULFILLED",
rejected: 'REJECTED'
}
class Promise {
constructor(executor) { // executor就是new Promise((reslove,reject)=>{}) 传入的(reslove,reject)=>{...}函数
this.status = STATUS.pennding // 当前的状态 创建时为pennding
this.value = null // 当前值
this.reason = null // 失败原因
this.onReslovedCallbacks = [] // 存放成功的回调函数队列 ---> 新加的代码
this.onRejectedCallbacks = [] // 存放失败的回调函数队列 ---> 新加的代码
// reslove 函数
const reslove = (value) => {
if (this.status === STATUS.pennding) {
this.status = STATUS.fulfilled // 将状态置为fulfilled
this.value = value
// 执行所有订阅的fulfilled函数
this.onReslovedCallbacks.forEach(fn => fn()) // ---> 新加的代码
}
}
// reject 函数
const reject = (reason) => {
if (this.status === STATUS.pennding) {
this.status = STATUS.rejected // 将状态置为fulfilled
this.reason = reason
// 执行所有订阅的rejected函数
this.onRejectedCallbacks.forEach(fn => fn()) // ---> 新加的代码
}
}
// 捕获异常 用于处理用户resolve/reject了抛出的错误,例如throw Error('xxxxx')
try {
executor(resolve, reject)
} catch (err) {
reject(err)
}
}
then(onFulfilled, onRejected) {
// 成功的回调
if (this.status === STATUS.fulfilled) { // 只有状态为fulfilled时才能执行onFulfilled方法,用户调用resolve时会将状态改为fulfilled
onFulfilled(this.value)
}
// 失败的回调
if (this.status === STATUS.rejected) { // 只有状态为rejected时才能执行onRejected方法,用户调用reject时会将状态改为rejected
onRejected(this.reason)
}
// 如果是等待状态,就把res,rej的处理回调函数放入对应的队列里 ---> 下面是新加的代码
if (this.status === STATUS.pennding) {
// 放入成功回调队列
this.onResolvedCallbacks.push(() => { // 切片: 将传入的函数用一个新函数包起来,可以在这个新函数里面写统一的额外逻辑(aop)
// 这里可以写额外的逻辑
onFulfilled(this.value)
})
// 放入失败回调队列
this.onRejectedCallbacks.push(() => { // 切片: 将传入的函数用一个新函数包起来,可以在这个新函数里面写统一的额外逻辑(aop)
// 这里可以写额外的逻辑
onRejected(this.reason)
})
}
}
}
// case2
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 1000)
setTimeout(() => {
reject('error')
}, 2000)
})
promise.then(
value => {
console.log(value, '1')
},
err => {
console.log(err)
}
)
// => 一秒后:
// success1
到现在已经实现了最基本的能异步Promise类,但是还有很多规范功能没有,继续往下看 PromiseA+ 规范要让我们实现的功能。
2.2 Promise 的 then() 方法
1. 一个Promise必须提供一个then方法去访问当前或者最终的value或者reason。
2. 一个Promise的then方法接受两个参数:onFulfilled 和 onRejected。
Promise.then(onFulfilled, onRejected)
2.2.1 onFulfilled 和 onRejected 都是可选的参数:
2.2.1.1 如果
onFulfilled不是一个函数,它必须被忽略。
2.2.1.2 如果onRejected不是一个函数,它必须被忽略。
2.2.2 如果 onFulfilled 是一个函数:
2.2.2.1
promise是fulfilled状态时它必须被调用,并且promise的value作为它的第一个参数。
2.2.2.2 它一定不能在promise进入fulfilled状态之前被调用。
2.2.2.3 它一定不能够调用超过一次。
2.2.3 如果 onRejected 时一个函数:
2.2.3.1
promise是rejected状态时它必须被调用,并且promise的reason作为它的第一个参数。
2.2.3.2 它一定不能在promise进入rejected状态之前被调用。
2.2.3.3 它一定不能够调用超过一次。
2.2.4 直到执行上下文堆栈仅包含平台代码之前,onFulfilled 或 onRejected 不能够被调用。
2.2.5 onFulfilled 和 onRejected 必须以函数的形式被调用(即没有this值)。
2.2.6 then 可以在同一个promise被调用多次:
2.2.6.1 当 promise 处于
fulfilled状态时,各个onFulfilled回调必须按其原始调用的顺序执行。
2.2.6.2 当 promise 处于rejected状态时,各个onRejected回调必须按其原始调用的顺序执行。
2.2.7 then 必须返回一个promise:
promise2 = promise1.then(onFulfilled, onRejected);
2.2.7.1 如果
onFulfilled或onRejected返回一个值x,运行Promise解决程序。
2.2.7.2 如果onFulfilled或onRejected抛出一个意外e,promise2必须以e为reason被rejected。
2.2.7.3 如果onFulfilled不是一个函数并且promise1处于fulfilled状态,promise2必须以与promise1同样的value转变到fulfilled状态。
2.2.7.4 如果onRejected不是一个函数并且promise1处于rejected状态,promise2必须以与promise1同样的reason转变到rejected状态。
根据2.2then方法描述信息可以得出以下信息:
1.每个Promise需要有一个then方法,传入两个回调函数作为参数onFulfilled, onRejected,非必传,但是不会影响下一个then的传值。
2.每次调用then必须返回一个promise2,是一个全新promise的实例,如果promise2里reslove/reject了一个新值x,则把当前值x当参数传递到下一个then的回调函数里。
思考:要想实现这样一个需求,返回的promise必须是一个新的实例,因为promise的状态是不可逆的,将新的promise的reslove和reject处理上一个promise返回的值。而且在没有传onFulfilled 和 onRejected函数,那么就要自己包一层函数并且该函数的返回值是上一个promise返回的值。
代码如下:
// 定义状态枚举
const STATUS = {
pennding: 'PENNDING',
fulfilled: "FULFILLED",
rejected: 'REJECTED'
}
class Promise {
constructor(executor) { // executor就是new Promise((reslove,reject)=>{}) 传入的(reslove,reject)=>{...}函数
this.status = STATUS.pennding // 当前的状态 创建时为pennding
this.value = null // 当前值
this.reason = null // 失败原因
this.onResolvedCallbacks = [] // 存放成功的回调函数队列
this.onRejectedCallbacks = [] // 存放失败的回调函数队列
// resolve 函数
const resolve = (value) => {
if (this.status === STATUS.pennding) {
this.status = STATUS.fulfilled // 将状态置为fulfilled
this.value = value
// 执行所有订阅的fulfilled函数
this.onResolvedCallbacks.forEach(fn => fn())
}
}
// reject 函数
const reject = (reason) => {
if (this.status === STATUS.pennding) {
this.status = STATUS.rejected // 将状态置为fulfilled
this.reason = reason
// 执行所有订阅的rejected函数
this.onRejectedCallbacks.forEach(fn => fn())
}
}
// 捕获异常 用于处理用户resolve/reject了抛出的错误,例如throw Error('xxxxx')
try {
executor(resolve, reject)
} catch (err) {
reject(err)
}
}
// -->重写了then函数,用promise包起来了
then(onFulfilled, onRejected) {
// 处理onFulfilled, onRejected为空的情况,防止then丢失上一个then返回的值
onFulfilled = typeof onFulfilled == 'function' ? onFulfilled : x => x;
onRejected = typeof onRejected == 'function' ? onRejected : err => { throw err }
// 每次调用then都返回一个全新的promise,把之前收集then回调函数的逻辑放到 new Promise()里面
const promise2 = new Promise((resolve, reject) => {
// 成功的回调
if (this.status === STATUS.fulfilled) {
try {
// resolve的返回值传给新的promise的结果
const x = onFulfilled(this.value)
resolve(x)
} catch (err) {
// reject的返回值传给新的promise的失败的结果
reject(err)
}
}
// 失败的回调
if (this.status === STATUS.rejected) {
try {
const x = onRejected(this.reason)
resolve(x) // onRejected返回的普通值会作为下一个then的reslove的值(promise规范)
} catch (err) {
reject(err)
}
}
// 如果是等待状态,就把res,rej的处理回调函数放入对应的队列里
if (this.status === STATUS.pennding) {
// 放入成功回调队列
this.onResolvedCallbacks.push(() => {
// 额外的逻辑
if (this.status === STATUS.fulfilled) {
try {
const x = onFulfilled(this.value)
resolve(x)
} catch (err) {
reject(err)
}
}
})
// 放入失败回调队列
this.onRejectedCallbacks.push(() => {
try {
const x = onRejected(this.reason)
resolve(x)
} catch (err) {
reject(err)
}
})
}
})
// 返回新的promise供下一个then调用
return promise2;
}
}
// case3
const promise = new Promise((reslove, reject) => {
setTimeout(() => {
reslove('success')
}, 1000)
})
promise.then(
value => {
console.log(value, '1')
return 'returnValue'
}
).then(
value => {
console.log(value, '2')
return 'otherValue'
}
).then().then( // 空then不会影响return的值
value => console.log(value, '3')
)
// => 一秒后:
// success1
// returnValue2
// otherValue3
// case4
const promise1 = new Promise((reslove, reject) => {
setTimeout(() => {
reject('error')
}, 1000)
})
promise1.then(
value => { },
err => {
console.log(err, 'err')
throw new Error('出错了')
}
).then(
value => {
console.log(err, 's1')
},
err => {
console.log(err, 'e1')
}
).then().then(
value => {
console.log(err, 's2')
},
err => {
console.log(err, 'e2')
}
)
// => 一秒后:
// error
// Error 出错了
// 后面的不再执行了
到目前为止已经基本实现了Promise的功能,能多个then链式调用,并且then里面return的值也能正确的传到下一个then,而且then里面throw Error也能正确的打断后面的then。
但是还有一些重要的功能没有,不能跟标准的Promise混用,例如then里面如果return的是Promise实例resolve/reject的值能不能正常传递到下一个then。
2.3 Promise 的解决程序
promise解决程序是一个抽象的操作,它把一个 promise 和一个 value 作为输入,我们将这个表示为 [[Resolve]](promise, x)。
如果 x 是一个 thenable ,它将会试图让 promise 采用 x 的状态,前提是x的行为至少有点像一个 promise。否则,它将会用值 x执行promise。
对这些 thenable 的处理使得与 promise 实现方式能够去互相操作。只要它们公开了符合 Promise/A+ 的 then 方法。它还使得 promises/A+ 实现方式能够采用合理的 then 方法去“同化”不一致的实现方式。
为了运行[[Resolve]](promise, x),执行以下步骤:
2.3.1 如果 promise 与 x 是同一个对象,以 Tyeperror 作为 reason 去 reject promise。
2.3.2 如果 x 是一个 promise,使用它的状态:
2.3.2.1 如果
x处于pending状态,promise必须保持pending状态直到x处于fulfilled或者rejected状态。
2.3.2.2 如果x处于fulfilled状态,以相同的value去fulfill promise。
2.3.2.3 如果x处于rejected状态,以相同的reason去reject promise。
2.3.3 否则,如果 x 是一个对象或者函数:
2.3.3.1 让
then作为x.then。
2.3.3.2 如果取属性x.then会导致抛出异常e,则以e为reason reject promise。
2.3.3.3 如果then是一个函数,让x作为this调用它,第一个参数为resolvePromise,第二个参数为rejectPromise,然后:2.3.3.3.1 如果使用
valuey调用resolvepromise时,运行[[Resolve]](promise, y)。
2.3.3.3.2 如果使用reasonr调用rejectPromise时,也用reject promise。
2.3.3.3.3 如果resolvePromise和rejectPromise都被调用了,或多次调用同一参数,那么第一个调用优先,其他的调用都会被忽略。
2.3.3.3.4 如果调用then的过程中抛出了一个意外e。2.3.3.3.4.1 如果
resolvePromise或者rejectPromise被调用了,那么忽略它。
2.3.3.3.4.2 否则,把e作为reason reject promise。
2.3.3.4 如果then不是一个函数,将x作为参数执行promise。
2.3.4 如果 x 不是一个对象或者函数,将x作为参数执行promise。 如果一个参与了thenable循环链的thenable去 resolve promise,这样[[Resolve]](promise, thenable)的递归性质最终会导致[[Resolve]](promise, thenable)会被再次调用,遵循上述算法将会导致无限递归。我们鼓励去实现(但不是必需的)检测这样的递归,并以TypeError作为reason去 reject Promise。
2.4 Promise 的平台代码
3.1 这里的“平台代码”指的是引擎,环境和 promise 实现代码。
实际上,这个要求保证了 onFulfilled 和 onRejected 将会异步执行,在事件循环之后,用一个新的堆栈来调用它。 这可以通过“宏任务”机制(如 settimeou t或 setimmediate )或“微任务”机制(如 mutationobserver 或 process.nextick)来实现。
由于 Promise 实现被视为平台代码,因此它本身可能包含一个任务调度队列或“trampoline”,并在其中调用处理程序。
3.2 也就是说,在 strict 模式下,这(指的是this)在它们内部将会是 undefined;在 sloppy 模式下,它将会是全局对象。
3.3 如果实现满足所有要求,则实现可能允许 promise2 == promise1。每个实现都应该记录它是否能够生成promise2 == promise1以及在什么条件下。
3.4 一般来说,只有当 X 来自当前的实现时,才知道它是一个真正的 promise。本条款允许使用特定于实现的方法来采用已知一致承诺的状态。
3.5 此过程首先存储对 x 的引用,然后测试该引用,然后调用该引用,避免多次访问x.then属性。这些预防措施对于确保访问器属性的一致性非常重要,访问器属性的值可能在两次检索之间发生更改。
3.6 实现方式中不应当在 thenbale 链中的深度设置主观的限制,并且不应当假设链的深度超过主观的限制后会是无限的。只有真正的循环才能导致 TypeError。如果遇到由无限多个不同 thenable 组成的链,那么永远递归是正确的行为。
思考:
在then有可能会return一个新的promise,这个时候需要将这个promise的reslove或reject的值传给下一个then,并且这个promise还有可能是别人实现的promise,要做兼容。
所以需要Promise解决函数它的核心的逻辑是要解析 x 的类型,来决定promise2 走成功还是失败,判断 x 的值决定promise2的关系 来判断有可能x是别人的promise可能别人的promise会出问题。
下面来实现reslovePromise函数
// 解析 x 的类型 来决定promise2 走成功还是失败
// promise2: 当前then里面promise实例
// x: 上一个then的返回值
// resolve,reject: 当前then里面promise的executor
function resolvePromise(promise2, x, resolve, reject) {
// 判断x 的值决定promise2 的关系 来判断有可能x 是别人的promise 可能别人的promise会出问题
if (x == promise2) {
return reject(new TypeError('出错了'))
}
// {}
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
// 只有x是对象或者函数才有可能是promise
let called = false; // 表示没调用过成功和失败
try {
let then = x.then; // 取x上的 then方法
if (typeof then == 'function') { // {then:function(){}}
then.call(x, y => { // x.then如果这样写 可能还会走get方法
// y 可能是一个promise, 递归解析y的值 直到他是一个普通值为止
if (called) return;
called = true
resolvePromise(promise2, y, resolve, reject);
}, r => {
if (called) return;
called = true
reject(r);
});
}else{
resolve(x); // 普通对象{}
}
} catch (e) {
if (called) return;
called = true
reject(e); // 走失败逻辑
}
} else {
// 如果不是 那一定是一个普通值
resolve(x);
}
}
所以之前的Promise类可以改成这样:
// 定义状态枚举
const STATUS = {
pennding: 'PENNDING',
fulfilled: "FULFILLED",
rejected: 'REJECTED'
}
class Promise{
constructor(executor) { // executor就是new Promise((reslove,reject)=>{}) 传入的(reslove,reject)=>{...}函数
this.status = STATUS.pennding // 当前的状态 创建时为pennding
this.value = null // 当前值
this.reason = null // 失败原因
this.onReslovedCallbacks = [] // 存放成功的回调函数队列
this.onRejectedCallbacks = [] // 存放失败的回调函数队列
// reslove 函数
const reslove = (value) => {
if (this.status === STATUS.pennding) {
this.status = STATUS.fulfilled // 将状态置为fulfilled
this.value = value
// 执行所有订阅的fulfilled函数
this.onReslovedCallbacks.forEach(fn => fn())
}
}
// reject 函数
const reject = (reason) => {
if (this.status === STATUS.pennding) {
this.status = STATUS.rejected // 将状态置为fulfilled
this.reason = reason
// 执行所有订阅的rejected函数
this.onRejectedCallbacks.forEach(fn => fn())
}
}
// 捕获异常 用于处理用户reslove/reject了抛出的错误,例如throw Error('xxxxx')
try {
executor(reslove, reject)
} catch (err) {
reject(err)
}
}
// -->重写了then函数,用promise包起来了
then(onFulfilled, onRejected) {
// 处理onFulfilled, onRejected为空的情况,防止then丢失上一个then返回的值
onFulfilled = typeof onFulfilled == 'function' ? onFulfilled : x => x;
onRejected = typeof onRejected == 'function' ? onRejected : err => { throw err }
// 每次调用then都返回一个全新的promise,把之前收集then回调函数的逻辑放到 new Promise()里面
const promise2 = new Promise((reslove, reject) => {
// 成功的回调
if (this.status == STATUS.fulfilled) {
setTimeout(() => { //因为resolvePromise里面用了promise2,这个时候promise2还没实例化完拿不到,所以要用定时器包起来
try {
let x = onFulfilled(this.value); // x可能是一个promise
resolvePromise(promise2, x, resolve, reject);
// resolve(x); // 用then的返回值 作为下一次then的成功结果
} catch (e) {
reject(e);
}
}, 0);
}
if (this.status === STATUS.rejected) {
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
}
if (this.status == STATUS.pending) {
// 订阅
this.onResolvedCallbacks.push(() => { // 切片
// todo .. 额外的逻辑
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
})
}
})
// 返回新的promise供下一个then调用
return promise2;
}
}
// case5
const promise = new Promise((reslove,reject)=>{
settimeout(()=>{
reslove('success')
},1000)
})
promise.then(
value=> {
console.log(value,'1')
return 'returnValue'
}
).then(
value=> console.log(value, '2')
return 'otherValue'
).then().then( // 空then不会影响return的值
value=> console.log(value, '3')
)
// => 一秒后:
// success1
// returnValue2
// otherValue3
// case6
let promise = new Promise((resolve, reject) => {
resolve('ok')
}).then(data => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(new Promise((resolve, reject) => {
setTimeout(() => {
resolve(data + ' resolved');
}, 2000);
}));
}, 1000)
})
}, err => {
});
promise.then(
data => {
console.log('success', data);
},
err => {
console.log('fail', err);
}
)
// => 2秒后:
// success ok resolved
至此一个简单的符合A+规范的Promise实现已经完成。理解了这些之后,可以实现一些自己的Promise小功能,例如deferred延迟函数:
Promise.prototype.deferred = function () {
let dfd = {} as any;
dfd.promise = new Promise((resolve,reject)=>{
dfd.resolve = resolve;
dfd.reject = reject;
})
return dfd;
}
// case7
let dfd = Promise.deferred(); // 延迟对象
setTimeout(()=>{
dfd.resolve('dfd')
},1000)
dfd.promise.then(
data=>{
console.log(data)
},
)
// 一秒后 打印dfd
也可以实现一下Promise.all方法
Promise.prototype.all = function(values) {
return new Promise((resolve, reject) => {
let arr = [];
let times = 0;
function collectResult(val, key) {
arr[key] = val;
if (++times === values.length) {
resolve(arr);
}
}
for (let i = 0; i < values.length; i++) {
let value = values[i];
if (value && isPromise(value)) {
value.then((y) => {
// 注意:这里要用计数器,不能直接push
collectResult(y, i)
}, reject)
} else {
collectResult(value, i);
}
}
})
}
完结撒花~