The overall structure meets A+ specification
It is not necessary to implement all of the Promise functions in the PromiseA+ specification, just the following structures:
function Promise (exectuer) {
this.promiseState = 'pending'
this.promiseResult = null
this.callback = []
function resolve (data) {
......
}
function rejected (data) {
......
}
try {
exectuer(resolve, reject)
} catch (error) {
reject(error)
}
}
Promise.prototype.then = function (onResolved, onRejected) {
......
}
Promise.prototype.catch = function (onRejected) {
......
}
Copy the code
Once the overall structure is in place, use scenarios for actual promises can be implemented step by step.
Satisfy the concrete implementation of A+ specification
// 声明构造函数Promise
function Promise(executer) {
/**
1. 添加属性Promise相关属性,promiseState,promiseResult,callback
2. promiseState用来保存Promise的状态:'pending','fulfilled','rejected'
3. promiseResult用来保存Promise最终的结果
4. callback是一个数组,用来保存resolve或reject方法在Promise中被异步调用时,then方法中的回调函数
5. 将这三个值放在this中,可以方便Promise实例在调用then方法时可以访问到
*/
this.promiseState = 'pending'
this.promiseResult = null
this.callback = []
// 保存this值,方便resolve和reject方法中访问当前对象
var self = this
// 创建resolve函数,作为executer的参数,下reject同
function resolve(data) {
/**
promise的状态只能从pending变成fulfilled或者rejected,只能改变一次
因此调用resolve和reject的时候要对promise的状态进行判断,防止下列情况而导致多次执行:
var promise = new Promise((resolve, reject) => {
resolve('success')
reject('error')
// 这种情况下若是不加判断,promise的状态会从pending变成fulfilled再变成rejected
})
*/
if (self.promiseState !== 'pending') return
/**
修改对象的状态(promiseState)和结果值(promiseResult)
因为这里是resolve函数,因此状态只能变成fulfilled,结果值跟resolve的参数相等
*/
self.promiseState = 'fulfilled'
self.promiseResult = data
/**
Promise是一个微任务,在node环境中使用process.nextTick()来实现模拟微任务
在浏览器环境中使用queueMicrotask()来实现模拟微任务
当然使用setTimeout来模拟同样也能满足A+规范,但setTimeout属于宏任务,感觉不太贴切
*/
queueMicrotask(() => {
/**
这里用来统一执行then中的回调函数,具体针对以下使用场景:
var promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
})
})
promise.then(value => {
console.log(value)
}).then(value => {
console.log(value)
}).then(value => {
console.log(value)
})
按照Promise的规范来说,只有当resolve或者reject执行之后改变了promise的状态,
才能执行promise.then()中的回调,但是在上面的这种情况下,
resolve方法被放到了setTimeout中执行,变成了异步执行,
根据JS的执行顺序,promise.then()中的回调函数将会直接执行,
因此为了满足规范的要求,就需要在then执行时判断当前状态是否是pending,
如果是pending的话,就要将then中的回调函数都保存在callback中,
然后当resolve或者reject被调用时再在这里用forEach遍历并执行
*/
self.callback.forEach(item => {
item.onResolved()
})
})
}
// reject的原理跟resolve一样,不同之处就是将promiseState的值变成了rejected
function reject(data) {
if (self.promiseState !== 'pending') return
self.promiseState = 'rejected'
self.promiseResult = data
queueMicrotask(() => {
self.callback.forEach(item => {
item.onRejected(data)
})
})
}
/**
这里用try...catch来对throw抛出的异常进行监听,为了满足以下使用:
var promise = new Promise((resolve, reject) => {
throw 'error'
})
在上述情况下,不执行resolve和reject方法,也要能改变promise的状态为rejected,
因此就需要在executer执行时,用try...catch进行异常捕获,当捕获到错误时执行reject方法
*/
try {
/**
同步调用,执行器函数executer,这里是Promise同步执行的部分,
总的来说Promise的异步体现在then方法中,executer的执行其实是同步的
*/
executer(resolve, reject)
} catch (error) {
// 有异常时,改变promise状态为rejected
reject(error)
}
}
// 将then方法添加在Promise的原型中,满足实例promise的调用,如promise.then()
Promise.prototype.then = function (onResolved, onRejected) {
// 将当前对象this保存在self中,当前this指向即为Promise构造函数的实例对象
var self = this
/**
接下去是对参数onResolved和onRejected的判断和处理,因为then方法中的两个参数都需要是函数
当这两个参数都为函数时,不做处理,如果不是函数,那么就将其重新封装成函数,方便后续执行
*/
if (typeof onResolved !== 'function') {
onResolved = value => {
/**
这里的value值是为了针对以下情况:
var promise = new Promise((resolve, reject) => {
resolve()
}).then(res => {
return 'success'
}).then(4).then(res => {
console.log(res) // 'success'
})
这里就相当于将上述.then(4)变成了.then((value) => { return value })
这样就跟其他的then方法写法没什么两样了,并且value的值是上一个then中返回的'success'
如此就能让下一个then中的参数是这个'success'了
*/
return value
}
}
if (typeof onRejected !== 'function') {
onRejected = reason => {
/**
这里onRejected方法与onResloved不同的地方在于onRejected要throw一个reason而不是return
首先这里若是使用return,那么Promise将无法进行异常穿透,导致下列场景无法实现:
let promise = new Promise((resolve,reject) => {
reject('err')
}).then(res => {
console.log(1, res)
}).then(res => {
console.log(2, res) // 如果使用return,那么这里将会执行,异常将无法穿透
}).catch(reason => {
console.log(3, reason)
// 如果使用throw,那么异常将会逐层抛出,直到进入catch,或者then方法的onRejected为函数
})
*/
throw reason
}
}
// 创建一个新的promise实例,用作调用then之后的返回值,这样保证then可以链式调用
let promise2 = new Promise((resolve, reject) => {
// 判断调用了then方法的promise对象的状态,不同状态下对应不同的操作
if (self.promiseState === 'fulfilled') {
// 用queueMicrotask模拟微任务,具体上面resolve函数中有讲到
queueMicrotask(() => {
/**
用try...catch进行异常捕获,因为then中的回调有可能会抛出异常,比如:
new Promise((resolve, reject) => {
resolve()
}).then(value => {
throw 'Error'
})
在遇到上述情况时,就要对then中回调函数的执行进行监听,
抛出错误的时候执行reject方法将其状态改成rejected
*/
try {
/**
首先执行then中的回调函数,当然这里的onResolved一定是个函数,
因为在上面已经进行了对onResolved和onRejected的判断
同时onResolved的参数是上一个resolve的参数值,
而执行resolve函数时会将参数赋值给promiseResult,
因此这里onResolved的参数就是self.promiseResult
*/
var result = onResolved(self.promiseResult)
/**
为了then能够链式调用,因此要返回一个新的promise,
但是新的promise也要给它一个状态,fulfilled或者rejected,
不然的话下一个then方法中的函数将无法执行
这一步的简单实现其实是这样的:
if (result instancef Promise) {
result.then(value => {
resolve(value)
}, reason => {
reject(reason)
})
} else {
resolve(result)
}
简单来说就是看result是否是promise实例,如果是,那么通过result.then
来决定我们当前返回的promise的状态,并将它的参数value传入,
解决了下面这种在then中返回一个promise实例的情况:
new Promise((resolve, reject) => {
resolve()
}).then(res => {
return new Promise((resolve, reject) => {
resolve('success')
})
}).then(res => {
console.log(res) // 'success'
})
但是这个result还有其他的可能性,因此这里就统一放在resolvePromise函数中判断解决
*/
resolvePromise(promise2, result, resolve, reject)
} catch (error) {
reject(error)
}
})
}
if (self.promiseState === 'rejected') {
process.nextTick(() => {
try {
var result = onRejected(self.promiseResult)
resolvePromise(promise2, result, resolve, reject)
} catch (error) {
reject(error)
}
})
}
/**
判断是否为pending状态,这种状态下执行then方法往往是遇到以下场景:
var promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 1000)
})
promise.then(res => {
console.log(res)
}).then(res => {
console.log(res)
})
上述场景中,resolve方法被异步调用了,根据JS的执行顺序,promise.then将会被先执行,
而根据Promise规范,这里需要保证在Promise的状态被改变了之后再执行then中的方法,
因此要将then中的方法保存到callback数组中,然后在上面的resolve或者reject调用之后再执行
*/
if (this.promiseState === 'pending') {
this.callback.push({
onResolved: function () {
/**
这里的改造其实跟之前的改动是一样的,
只不过是在resolve和reject在异步中调用的时候,then的回调会放到这里执行,
在具体执行的时候,两边的执行逻辑其实是一样的,
就是执行的时机修改了一下
*/
try {
var result = onResolved(self.promiseResult)
resolvePromise(promise2, result, resolve, reject)
} catch (error) {
reject(error)
}
},
onRejected: function () {
try {
var result = onRejected(self.promiseResult)
resolvePromise(promise2, result, resolve, reject);
} catch (error) {
reject(error)
}
}
})
}
})
return promise2
}
/**
catch方法只是单独的对reject方法执行后的处理,而这个处理在then方法中都有实现,
因此只要调用then并将所需的参数传入就行,并且这时候catch会返回一个状态为fulfilled的promise
所以在下列场景中,catch执行完成后还会继续执行then:
new Promise((resolve, reject) => {
reject('err')
}).then(res => {
console.log(res)
}).catch(reason => {
console.log(reason)
return 'success'
}).then(res => {
console.log(res)
return 'success2'
}).then(res => {
console.log(res)
})
输出为err, success, success2
*/
Promise.prototype.catch = function (onRejected) {
return this.then(undefined, onRejected)
}
// 统一对then中onResolved和onRejected方法的返回值进行处理
function resolvePromise(promise2, x, resolve, reject) {
// 判断传入的promise2和x是否相等,防止死循环
if (promise2 === x) {
// 相等的话就报错,放在reject中直接改变promise状态为rejected
reject(new TypeError('Chaining cycle'))
}
// 当x不等于null,并且是object类型或者function类型时的情况
if (x && typeof x === 'object' || typeof x === 'function') {
// 定义一个常量used,判断
let used
// 捕获x.then这一步执行时候的错误,如果抛出错误就直接reject
try {
// 判断x是否存在then属性
let then = x.then
// 判断then是否是个函数
if (typeof then === 'function') {
/**
如果then是个函数,那么通过call去执行它,并且将执行对象指定为x,两个参数通过封装后传入,
这里就相当于改造x自己的then方法,让其像Promise自己提供的那个then方法一样执行
*/
then.call(x, (y) => {
/**
这里为了防止x.then是一个thenable对象,这样就可能发生resolve和reject都被调用的情况
所以要保证resolve和reject只能以先执行的那一个为准,类似以下情况,used的判断就有用了
let promise = new Promise((resolve, reject) => {
resolve()
}).then(res => {
return {
then: function(onResolved, onResolved) {
onResolved(3)
onResolved(5)
}
}
})
当used为true,说明已经下述代码已经执行过一次了,因此不再执行
*/
if (used) return
used = true
// 将参数y传入resolvePromise之后正常执行
resolvePromise(promise2, y, resolve, reject)
}, (r) => {
if (used) return
used = true
reject(r)
})
} else {
// 当x的then不是个函数时,就正常将x当作参数执行就可以了
if (used) return
used = true
resolve(x)
}
} catch (e) {
if (used) return
used = true
reject(e)
}
} else {
/**
当x不是对象或者函数的时候,就直接通过执行resolve(x)
以此来改变执行完then后所返回新的promise的状态
*/
resolve(x)
}
}
Copy the code
Tests whether the specification is met
PromiseA+ / PromiseA+ / PromiseA+ / PromiseA+ / PromiseA+ / PromiseA+ / PromiseA+ / PromiseA+ / PromiseA+
- First of all by
npm install promises-aplus-tests
Install dependencies - Edit the package.json file
{" name ":" the promise ", "version" : "1.0.0", "description" : "my promise", "main" : ". / promise. Js ", "scripts" : {" test ": "promises-aplus-tests promise" }, "author": "ITEM", "license": "ISC", "devDependencies": { "promises-aplus-tests": "^ 2.1.2"}}Copy the code
- Add the following code to your promise.js file:
module.exports = {
deferred() {
var resolve;
var reject;
var promise = new Promise(function (res, rej) {
resolve = res;
reject = rej;
})
return {
promise,
resolve,
reject
}
}
}
Copy the code
-
Run the NPM run test command
-
End result: a perfect fit
To summarize
- A Promise that complies with the specification itself may not perform exactly the same as a built-in Promise, as in the following code:
new Promise((resolve, reject) => { resolve() }).then(() => { console.log(0); return new Promise((resolve, reject) => { resolve(4) }) }).then((res) => { console.log(res) }) new Promise((resolve, reject) => { resolve() }).then(() => { console.log(1); }).then(() => { console.log(2); }).then(() => { console.log(3); }).then(() => { console.log(5); }).then(() =>{ console.log(6); }) // browser execution: 0,1,2,3,4,5,6 // actual implementation execution: 0,1,2,4,3,5,6Copy the code
- The implementation here satisfies the A+ specification, but falls short of A full Promise with built-in methods such as,
all
.race
Etc.