Promise我摞梗啦,系人睇过都知,我话噶

173 阅读6分钟

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来搭建后台服务过程中,第一个了解的也是关于异步的处理,也是一直备受关注的回调地狱。(在网上「回调地狱」搜了下图) image.png 之后在工作中迎来了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影响。罗列一下其规范:

  1. 如果onFulfilled或onRejected返回一个value x,则运行Promise Resolution程序[[Resolve]](promise2, x)
  2. 如果onFulfilled和onRejected抛出了一个exception e,则promise2必须以这个e作为reason rejected
  3. 如果onFulfilled不是一个函数且promise1已经fulfilled,则promise2也需以相同的value切换到fulfilled状态
  4. 如果onRejected不是一个函数且promise1已经rejected,则promise2也需以相同的reason切换到rejected状态

校验resolve参数value的合法性

根据规范也需要对value的合法性进行校验,在resolvePromise中对于「value x」的处理,规范提到:

  1. 如果x是promise本身,则抛出TypeError错误
  2. 如果x是一个promise,那么沿用它的state和result状态,也就是按promise的流程进行
  3. 如果x是一个Object或Function
    a. 尝试将then指向x.then
    b. 如果检索x.then抛出错误e,则将e作为reason reject
    c. 如果then是一个函数,则then.call(x, resolvePromise, rejectPromise)
  •   i.  假设用value y作参数调用resolvePromise,执行[[Resolve]](promise, y)  
      ii.  如果用reason r作参数调用rejectPromise,则以r为结果reject  
      iii.  如果同时调用了resolvePromise和rejectPromise,或者对同一个参数进行了多次调用,则第一个调用优先,并且任何进一步的调用都将被忽略  
      iv.  如果call抛出错误,则忽略所有执行的resolvePromise和rejectPromise,同时reject该错误  
    
    d. then不是函数,直接用x作为当前promise的fulfill value
  1. 如果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.png

来几道面试题试试

常规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的实现逻辑的思考与巩固,如果觉得有帮助,那就祝你不在这卷潮中被搅得细碎,如果觉得我写的有纰漏,欢迎指出,让我继续卷起来。