Promise我摞梗啦,系人睇过都知,我话噶
关于Promise的实现和各大面试要点全网随便一搜几乎已有完美答案,此时再去写一篇关于Promise相关的文章也不为别的,纯粹是对Promise的原理再熟悉熟悉,还有就是基于熬夜导致的记忆里衰退做备份。
本文所有的代码片段全部出自以下公众号文章:100 行代码实现 Promises/A+ 规范
正文
异步处理演进
在JQ或原生JS为主的前端项目中,与后台服务的API交互往往是用Ajax请求到数据后利用callback的形式将数据填充到页面中。
$.ajax({
url: 'domain.com/getdata',
method: 'GET',
success: function(data){
// do something
}
})
而我所学用Node来搭建后台服务过程中,第一个了解的也是关于异步的处理,也是一直备受关注的回调地狱。(在网上「回调地狱」搜了下图)
之后在工作中迎来了Vue,React,Angular框架化前端开发的阶段,ES6也将Promise纳入标准,于是接下来的工作中,每天处理前端异步的操作变成了以promise为主的处理方式。
this.$http.get('/getUser').then(user => {
this.$http.get('/getDevices', {'accout': user.id}).then(devices => {
// do something
})
})
然后写着写着,对于一些没有必要复用的逻辑也有了then模式的回调地狱。但这只是写法没有优化的问题。Promise比起callback的回调方式,给予我们开发讨论过程中一个更具语义化的解释:
🗣 Promise(执行了什么)then(然后要干什么)而现在javascript异步方案演进,从callback的方式到了async/await阶段。
Promise的实现与规范
谈及Promise的实现,首先要对其规范讲一讲,网上对Promise的实现也都是遵循Promise A+来实现的,我找了文中所用到的GitHub仓库:promise-aplus-impl/index.js at master · Lucifier129/promise-aplus-impl
- promise是一个包含then方法的对象或函数,该方法符合规范指定的行为
- thenable是一个包含then方法的对象或函数
- value值需是一个合法的任意JS值,包括undefined,thenable,promise
- exception用于throw抛出的值
- reason提示一个promise为什么被reject的原因
Promise state
首先一个promise一定是具备三种状态
- pending
- fulfilled
- rejected
其初始化pending状态最终只能向fulfilled或rejected转变,且状态的改变是不可变的。
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
💡 对于全局状态的定义可以用这种定义方式进行语义化说明。
构造Promise
function Promise(fun) {
// 初始状态值
this.state = PENDING
// Promise结果
this.result = null
// 事件处理队列,每一个Promise都有其独立的handlers队列
this.handlers = []
// 向fulfilled状态转变
let onFulfilled = value => transition(this, FULFILLED, value)
// 向rejected状态转变
let onRejected = reason => transition(this, REJECTED, reason)
// 初始化ignore
// ignore的作用在于处理resolve或reject过程中同一时间点调用了,或者对同一个参数进行多次调用
// 则以第一个调用为主,后续调用全部被忽略
// 另一作用是代码执行过程中一旦发生错误,被catch到后直接reject,原本的resolve和reject也直接忽略
let ignore = false
let resolve = value => {
if (ignore) return
ignore = true
// 校验resolve参数value的合法性
resolvePromise(this, value, onFulfilled, onRejected)
}
// 一旦reject我们只需要改变state和返回reason
let reject = reason => {
if (igonre) return
ignore = true
onRejected(reason)
}
try {
// 向fun"注入"resolve和reject方法,这也是为什么一个Promise默认固定参数都是resolve和reject
// Promise((resolve, reject) => {})
fun(resolve, reject)
} catch (error) {
reject(error)
}
}
Promise then的实现
Promise实现了then链式调用,不同于类似JQuery那样一直return this的实现,Promise在于返回值都是一个新的Promise,而每个Promise都具备then方法。对于值的传递,由handlers队列中相继的任务执行传递下去。
// then方法也是支持resolve/reject两个处理函数参数
Promise.prototype.then = function (onFulfilled, onRejected) {
// 返回一个新Promise
return new Promise((resolve, reject) => {
// 将Promise实例的onFulfilled,onRejected和then方法中新的resolve,reject一起存入到队列中
this.handlers.push({onFulfilled, onRejected, resolve, reject})
// 如果当前的Promise状态已转变,则开始处理队列任务
this.state !== PENDING && notifyAll(this)
})
}
notifyAll的效果在于处理规范中then的约束,在实现中使用setTimeout将执行上下文处理了一下。
💡 onFulfilled or onRejected must not be called until the execution context stack contains only platform code.// delay封装了setTimeout用于处理上下文问题
const notifyAll = delay(promise => {
let { handlers, state, result } = promise
while(handlers.length) {
notify(handlers.shift(), state, result)
}
})
// 遍历处理handlers队列任务
const notify = (handler, state, result) => {
let { onFulfilled, onRejected, resolve, reject } = handler
try {
if (state === FULFILLED) {
isFunction(onFulfilled) ? resolve(onFulfilled(result)) : resolve(result)
} else if (state === REJECTED) {
isFunction(onRejected) ? resolve(onRejected(result)) : reject(result)
}
} catch (error) {
reject(error)
}
}
then方法返回的Promise也有自己的state和result,它们受传递进来的onFulfilled和onRejected影响。罗列一下其规范:
- 如果onFulfilled或onRejected返回一个value x,则运行Promise Resolution程序[[Resolve]](promise2, x)
- 如果onFulfilled和onRejected抛出了一个exception e,则promise2必须以这个e作为reason rejected
- 如果onFulfilled不是一个函数且promise1已经fulfilled,则promise2也需以相同的value切换到fulfilled状态
- 如果onRejected不是一个函数且promise1已经rejected,则promise2也需以相同的reason切换到rejected状态
校验resolve参数value的合法性
根据规范也需要对value的合法性进行校验,在resolvePromise中对于「value x」的处理,规范提到:
- 如果x是promise本身,则抛出TypeError错误
- 如果x是一个promise,那么沿用它的state和result状态,也就是按promise的流程进行
- 如果x是一个Object或Function
a. 尝试将then指向x.then
b. 如果检索x.then抛出错误e,则将e作为reason reject
c. 如果then是一个函数,则then.call(x, resolvePromise, rejectPromise)
-
d. then不是函数,直接用x作为当前promise的fulfill valuei. 假设用value y作参数调用resolvePromise,执行[[Resolve]](promise, y) ii. 如果用reason r作参数调用rejectPromise,则以r为结果reject iii. 如果同时调用了resolvePromise和rejectPromise,或者对同一个参数进行了多次调用,则第一个调用优先,并且任何进一步的调用都将被忽略 iv. 如果call抛出错误,则忽略所有执行的resolvePromise和rejectPromise,同时reject该错误
- 如果x不是Object或Function,直接用x作为当前promise的fulfill value
const resolvePromise = (promise, result, resolve, reject) => {
if (result === promise) {
let reason = new TypeError('Can not fulfill promise with itself')
return reject(reason)
}
if (isPromise(result)) {
return result.then(resolve, reject)
}
if (isThenable(result)) {
try {
let then = result.then
if (isFunction(then)) {
return new Promise(then.bind(result)).then(resolve, reject)
}
} catch (error) {
return reject(error)
}
}
resolve(result)
}
transition状态转换
const transition = (promise, state, result) => {
if (promise.state !== PENDING) return
promise.state = state
promise.result = result
notifyAll(promise)
}
一个完整的Promise实现大概如此,对于其另外拓展的resolve,reject,catch等方法,可以访问原作者的GitHub仓库查看,如果能再给个star的话。
Promise执行流程
上述代码大致以Promise A+的规范实现,我们先尝试以一个简单的例子来解释代码执行的流程。
let promise1 = new Promise((resolve, reject) => {
resolve(1)
reject(new Error('something wrong'))
})
promise1.then(result => {
console.log(result)
return Promise.resolve(2)
}, error => {
// it may receive the error about 'something wrong'
console.log(error)
}).then(result1 => {
console.log(result1)
return 3
}).then(result2 => {
console.log(result2)
})
来几道面试题试试
常规promise执行顺序
const promise = new Promise((resolve, reject) => {
console.log(1)
resolve()
console.log(2)
})
promise.then(() => {
console.log(3)
})
console.log(4)
- 运行结果
1, 2, 4, 3
这里在于promise内是立即执行的,对于then的处理以代码的视角来看是被setTimeout挂起了,所以先执行了4再到3。
then传递参数
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log)
- 运行结果
1
在then的规范中有提到,如果x不是Object或Function,直接用x作为当前promise的fulfill value。因此直接发生值穿透,将1传递到console.log函数中执行输出。
致谢
以上是我重新回顾Promise的实现逻辑的思考与巩固,如果觉得有帮助,那就祝你不在这卷潮中被搅得细碎,如果觉得我写的有纰漏,欢迎指出,让我继续卷起来。