本文参考:剖析 Promise 内部机制
Promise 是一种对异步操作的封装,在异步操作执行成功或者失败时执行指定方法。将横向的异步调用转换为纵向,因此更符合人类的思维方式。
三种生命状态
一个 Promise 对象具备三种生命状态:pending、fulfilled和 rejected。只能从最初的 pending 到 fulfilled 或者 rejected,而且状态的改变是不可逆的
。
工作原理
我们先简单看下 Promise 的工作原理。
image.png
Promise 大致的工作流程是
-
创建 Promise 对象 => 进入等待处理阶段 Pending
-
处理完成后,切换到 Fulfilled 状态/ Rejected 状态
-
根据状态,执行 then 方法/执行 catch 方法 内的回调函数
then 方法返回新的 Promise
,此时就支持链式调用
这里创建一个 Promise 对象,Promise 内部维系着 resolve 和 reject 方法,resolve 会让 Promise 对象进入 Fulfilled 状态
,并将 resolve 方法的第一个参数传给后续 then 所指定的 onFulfilled 函数。reject 方法同理,只不过是切换到 Rejected 状态,并将参数传给 catch 所指定的 onRejected 函数。
一步步打造心中的 Promise
基础实现
先抛开 rejected,实现一个 Promise 的调用链的简单代码如下:
function Promise (fn) {
var deferreds = []
this.then= function (onFulfilled){
deferreds.push(onFulfilled) // 将 onFulfilled 函数压入 deferreds 队列中
}
function resolve (value) {
deferreds.forEach(deferred => {
deferred(value) // 执行deferreds 中的 onFulfilled 函数队列
})
}
fn(resolve)
}
深入理解上面代码逻辑:
-
then 方法将 onFulfilled 函数压入 deferreds 队列中。
-
将 resolve 传给创建 Promise 时传入的函数,resolve 的作用是将 deferreds 中的 onFulfilled 函数队列逐一执行。
但是这段代码暴露出一个严重的问题,如果 Promise 执行的是同步代码,resolve 是早于 then 方法的执行,这样造成一个问题:then 还没有及时把 onFulfilled 函数压入队列,此时 deferreds 还是空数组,resolve 执行后,后续注册到 deferreds 数组内的 onFulfilled 函数将不再执行。
这里我们可以把 deferreds 数组视为水桶,onFulfilled 视为饮用水,resolve 视为开关。then 操作就是将饮用水一点点地注入到水桶中。想想我们还没将水加到水桶中(执行 then 操作)就打开开关(执行 resolve),这肯定是接不到水的。
解决的办法就是将 resolve 函数的执行改为异步。
Promises/A+ 规范明确要求回调需要通过异步方式执行,保证一致可靠的执行顺序。通过 setTimeout 方法,我们可以轻松实现:
function resolve (value) {
setTimeout(()=> {
pending.forEach(deferred => {
deferred(value)
})
},0)
}
这样我们就可以把 resolve 执行放到下一个时钟周期。
引入状态
按照 Promise 规范,我们需要引入三种互斥的状态:pending、fulfilled、rejected。
执行 resolve 会将 pending 状态切换到 fulfilled,在此之后添加到 then 的函数都会立即被调用。
现在我们的代码如下:
function Promise(fn) {
const deferreds= []
var state ='pending'
var value = null
this.then = function (onFulfilled) {
if (state==='pending') {
deferreds.push(onFulfilled)
return this
}
onFulfilled(value)
return this
}
function resolve (_value) {
state = 'fulfilled' // 状态切换到 fulfilled
value = _value
setTimeout(()=> {
deferreds.forEach(deferred => {
deferred(value)
})
},0)
}
fn(resolve)
}
有了上面的基础,我们可以简单地调用 Promise:
asyncreadfile('/README. md','utf-8').then(data => {
console. log( data)
})
为了串行 Promise,我们在 then 中返回 this,并设置一个 value 来保存传给 resolve 的值。
asyncreadfile('/README. md', 'utf-8').then(data => {
console. log(data)
return asyncreadfile('/package.json','utf-8')
}). then(data => {
console. log( data)
})
像上面这样调用,虽然可以通过,但是两次输出的 data 是相同的值,并不是真正意义上的链式调用。
串行 Promise
只要 then 方法每次调用都返回一个 Promise 对象,前一个 Promise 对象持有后一个 Promise 对象的 resolve 方法,这样串行就变得非常简单了。
这里需要对 then 方法进行改造:
this.then= function (onFulfilled) {
return new Promise( resolve => { // 每次都返回一个Promise对象
handle({ onFulfilled, resolve })
})
}
function handle (deferred) {
if (state==='pending') {
deferreds.push(deferred)
return
}
const ret =deferred.onFulfilled(value)
deferred.resolve(ret)
}
这里完成的主要任务是:
then 方法中返回一个新的 Promise 对象
,这样每次执行 then 方法,都返回一个 Promise 对象,让链式调用成为可能。
新创建的 Promise 对象调用上一级 Promise 的 handle 方法,传递自身的 resolve 方法和当前的 onFulfilled 函数。
handle 相比之前的 then 多了一行 deferred.resolve(ret),这一步是链式调用的关键点。此刻的 resolve 是下一级 Promise 的方法,上一级 Promise 执行这段方法调用,就开启了链式调用。
我们继续重构前面的 Promise 代码,这里主要修改的是 resolve 方法。
function Promise (fn) {
const deferreds = []
var state = 'pending'
var value = null
this.then= function (onFulfilled) {
// then方法永远会返回一个Promise对象
return new Promise(resolve => {
// handle为上一级Promise的方法
handle({ onFulfilled, resolve })
})
}
function handle (deferred) {
if (state === 'pending') {
// then方法将 deferred传入时,先压入到 deferreds中
deferreds.push( deferred)
return
}
// 执行 Bridge Promise前一个 Promise对象的 then 方法的onFulfilled函数
const ret = deferred,.onFulfilled(value)
// resolve执行 deferreds中的onFulfilled方法,即下一
// 个 Bridge Promise的then中的回调函数
deferred.resolve(ret)
}
function resolve(_value) {
// 如果是Promise对象
if (_value &&(typeof _value ==='object' || typeof
_value === 'function')) {
const then = _value.then
if (typeof then === 'function') {
// 将 resolve延迟到 promise执行完毕后调用,切换
Bridge Promise的状态
then.call(_value, resolve)
return
}
}
// 如果是其它值
state = 'fulfilled'
value = _value
setTimeout(()=> {
deferreds.forEach(deferred => {
handle(deferred)
})
},0)
}
fn(resolve)
}
Promise 具体流程
-
实例化一个最初的 Promise 对象,设置最初的状态为 pending。
-
通过 then 方法,创建一个新的 Promise 对象,由于上一级 Promise 暂时处于 pending 状态,当前 then 方法的 onFulfilled 函数和新 Promise 的 resolve 方法放入到上一级 Promise 的 deferreds 数组中。
-
这样就形成这样一个画面:第一个 Promise 被实例化,调用 then 方法。then 会返回一个新的 Promise 对象,在上一个 then 方法的基础上继续通过新 Promise 的 then,形成一条调用链。 每一个被创建出来的新 Promise 的 resolve 都将传给上一级的 Promise 的 deferreds 数组来维护。
-
在第一个 Promise 对象的回调函数中执行异步操作,完成后调用 Promise 的 resolve 方法。
-
resolve 允许传入一个参数,该参数的值通过 Promise 内部的 value 变量维护。resolve 会把 Promise 的状态修改为 fulfilled,然后异步调用 handle 依次处理 deferreds 数组中的每一个 deferred。
-
此时第一个 Promise 的状态在上一步骤中被改为 fulfilled,于是 handle 主要完成的工作是,执行 deferred 的 onFulfilled 函数,并调用下一个 Promise 的 resolve 方法。
-
下一个 Promise 的 resovle 在上一级被执行成功后,同样会将状态切换到 fulfilled ,重复步骤 6 直到结束。
网友评论