阅读 166

带你认识和理解Promise

Promise是什么?

promise,承诺的意思。意思就是,JS执行线程 承诺 在将来的某个时刻执行某一块代码。
也就是说,你可以把一段将来才需要执行的代码,通过promise,把它放进异步任务队列中,在将来的某个时刻执行。So,promise就是用来写Javascript异步代码的。

有什么用?

这就要从Javascript的异步任务说起了。

比如在实际编程中经常需要使用ajax向服务器请求数据,成功获取到数据后,才开始处理数据。于是代码分成获取数据部分和处理数据部分。在以前,处理数据逻辑是通过回调函数来实现的,像这样:

function getData ( cb ) {
    // 获取数据
    let xhr = new XMLHttpRequest()
    xhr.open('post', 'http://testdomain.com/api/getname', true)
    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4 && xhr.status === 200) cb(xhr.responseText)
        else alert(xhr.statusText)
    }
    xhr.send()
}

getData( data => {
    // 用于处理数据的回调函数
    alert(data)
} )
复制代码

现在假设有多个异步任务,且任务间有依赖关系(一个任务需要拿到另一个任务成功后的结果才能开始执行)的时候,回调的方式写出来的代码就会像下面这样:

getData1( data1 => {
    getData2( data1, data2 => {
        getData3( data2, data3 => {
            getData4( data3, data4 => {
                getData5( data4, data5 => {
                    // 终于取到data5了
                })
            })
        })
    })
})
复制代码

这种代码被称为回调地狱或者回调金字塔。如果逻辑复杂,阅读代码的时候跳来跳去,令人头秃。而且若是想要换一下执行顺序,代码修改起来就比较麻烦了。
Promise —— 优雅解决回调嵌套!
上面的代码如果用promise改写一下:

// 先把getData们都改写成返回promise对象的函数
getData1()
.then(getData2)
.then(getData3)
.then(getData4)
.then(getData5)
.then(data5 => {
    // 取到最终的data5了
})
复制代码

这样线性的代码,流程清晰,便于阅读,要修改执行顺序也很容易。

Promise 好使吧?来来来,我们一起来好好了解下它。 在这里插入图片描述 先来了解一下 Promise 是怎么被使用的:

Promise 基本用法

let p = new Promise((resolve, reject) => {
    // 做一些事情
    // 然后在某些条件下resolve,或者reject
    if (/* 条件 */) {
        resolve()
    } else {
        reject()
    }
}

p.then(() => {
    // 如果p的状态被resolve了,就进入这里
}, () => {
    // 如果p的状态被reject
})
复制代码

解释一下

  1. 生成Promise实例
  • 构造函数接受一个函数作为参数
  • 参数函数接受两个函数(resolve和reject)作为参数
  • resolve函数的作用是:把Promise实例 p 的状态修改为 fulfilled,然后执行then方法里注册的onFulfilled函数,也就是then方法的第一个参数
  • reject函数的作用是:把Promise实例 p 的状态修改为 rejected,然后执行then方法里注册的onRejected函数,也就是then方法的第二个参数
  • 调用构造函数得到实例p的同时,作为参数的函数会立即执行
  • 参数函数被调用时,实际上传给它的参数是在Promise类里面实现的两个函数resolve和reject
  1. 调用实例的then方法(调用then方法可以为实例 p 注册两个状态回调函数)

OK,了解到Promise的用法,我们就满足了吗?我们会就这样停下学习的脚步吗?

Promise 的一些特性

  1. Promise 的状态
  • Promise 的值是指状态改变时传递给回调函数的值。
  • Promise 对象存在以下三种状态:
    i. Pending(进行中)
    ii. Fulfilled(已成功)
    iii. Rejected(已失败)

    状态只能由 Pending 变为 Fulfilled 或由 Pending 变为 Rejected,且状态改变之后不会在发生变化,会一直保持这个状态。

  1. then 方法支持多次调用以注册多个回调函数
  2. then 方法支持链式调用

结合以上 Promise 的使用和特性,我们尝试自己实现一个 Promise 类。实现过程中,我们会重点讲解resolvethen这两个核心且常用的方法。
Promises A+规范(promisesaplus.com/)规定了 Promise 的原理、源代码规范,我们的实现将基于这个规范。

我的Promise

// 判断变量否为function
  const isFunction = variable => typeof variable === 'function'
  // 定义Promise的三种状态常量
  const PENDING = 'PENDING'
  const FULFILLED = 'FULFILLED'
  const REJECTED = 'REJECTED'

  class Promise {
    constructor (fn) {
      if (!isFunction(fn)) {
        throw new Error('Promise must accept a function as a parameter')
      }
      // 添加状态
      this._status = PENDING
      // 添加值
      this._value = undefined
      // 添加成功回调函数队列
      this._fulfilledQueues = []
      // 添加失败回调函数队列
      this._rejectedQueues = []
      // 执行handle
      try {
       // 传进来的参数函数会被立即执行,并将_resolve和_reject函数作为参数传给它
        fn(this._resolve.bind(this), this._reject.bind(this)) 
      } catch (err) {
        this._reject(err)
      }
    }

    // then方法: 注册一个成功/失败回调函数,即,往promise对象的回调函数队列中添加回调函数
    then (onFulfilled, onRejected) {}
    // 添加catch方法
    catch (onRejected) {}

    // resovle函数的作用:修改promise对象的值和状态,执行then方法注册的成功回调函数队列
    _resolve (val) {}
    // reject函数的作用:修改promise对象的值和状态,执行then方法注册的失败回调函数队列
    _reject (err) {}
}
复制代码

Emmm,差不多就是这样了。
我们再来看看Promise类中的那些方法要怎么写。

then

 // then方法: 注册一个成功/失败回调函数,即,往promise对象的回调函数队列中添加回调函数
    then (onFulfilled, onRejected) {
     if (onFulfilled) this._fulfilledQueues.push(onFulfilled)
     if (onRejected) this._rejectedQueues.push(onRejected)
     // 因为then方式支持链式调用,所以要返回一个promise对象,我们先简单的返回 promise 对象自己
     return this
    }
复制代码

catch

    // 这个简单,就不多说了
    catch (onRejected) {
        return this.then(undefined, onRejected)
    }
复制代码

_resolve

// resovle函数的作用:修改promise对象的值和状态,执行then方法注册的成功回调函数队列
_resolve (val) {
  // 已经被resolve过的promise对象不能再次被resolve
  if (this._status !== PENDING) return
  // 为什么resolve 加setTimeout?
  // 2.2.4规范 onFulfilled 和 onRejected 只允许在 execution context 栈仅包含平台代码时运行.
  // 这里的平台代码指的是引擎、环境以及 promise 的实施代码。实践中要确保 onFulfilled 和 onRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。
  setTimeout(() => {   
    this._value = val
    this._status = FULFILLED
    let cb;
    while (cb = this._fulfilledQueues.shift()) {
      cb(val)
    }
  }, 0)
}
复制代码

_reject

// reject函数的作用:修改promise对象的值和状态,执行then方法注册的失败回调函数队列
_reject (err) {
  if (this._status !== PENDING) return
  setTimeout(() => {   
    this._value = err
    this._status = REJECTED
    let cb;
    while (cb = this._rejectedQueues.shift()) {
      cb(err)
    }
  }, 0)
}
复制代码

Promise 的精髓

从表面上看,Promise 只是能够简化层层回调的写法,而实质上,Promise 的精髓是状态,用“维护状态”“传递状态”的方式来使得回调函数能够及时调用,它比传递 callback 函数要简单、灵活的多。

还记得我们前面那段代码吗?

// 先把getData们都改写成返回promise对象的函数
getData1()
.then(getData2)
.then(getData3)
.then(getData4)
.then(getData5)
.then(data5 => {
    // 取到最终的data5了
})
复制代码

我们来看看then方法要怎么实现,才能支持上面的用法(getData1的值传给getData2,getData2传给getData3……)。我们之前写的还是太简单了,而且只是“return this”也不支持这种状态的传递

then (onFulfilled, onRejected) {
  let self = this
  let promise2 // then要返回的新promise
  promise2 = new Promise((resolve, reject) => {
    let pushedOnFulfilled = () => {
      setTimeout(() => {
        // 当执行成功回调的时候,可能会出现异常,那就用这个异常作为promise2的错误的结果
        try {
          if (isFunction(onFulfilled)) {
            let x = onFulfilled(self._value)
            // x 有可能是Promise对象,也有可能是数字、字符串等普通类型的数据
            if (x instanceof Promise) {
              x.then(resolve, reject)
            } else {
              resolve(x)
            }
          } else {
            // 2.2.7.3规范:onFulfilled如果不是function则值会穿透,传入下一个then中
            resolve(self._value)
          }
        } catch (e) {
          reject(e)
        }
      }, 0)
    }
    let pushedOnRejected = () => {
      setTimeout(() => {
        try {
          if (isFunction(onRejected)) {
            let x = onRejected(self._value)
            if (x instanceof Promise) {
              x.then(resolve, reject)
            } else {
              resolve(x)
            }
          } else {
            // 2.2.7.4规范:onRejected如果不是function则错误会穿透,下一个promise也就是promise2会以相同的理由被reject
            reject(self._value)
          }
        } catch (e) {
          reject(e)
        }
      }, 0)
    }
    
    if (self._status === FULFILLED) {
      pushedOnFulfilled()
    } else if (self._status === REJECTED) {
      pushedOnRejected()
    } else if (self._status === PENDING) {
      self._fulfilledQueues.push(pushedOnFulfilled)
      self._rejectedQueues.push(pushedOnRejected)
    }
  })
  return promise2
}
复制代码

then讲完,考虑到resolve的参数也有可能是Promise对象,我们再来完善一下resolve

_resolve (val) {
  if (this._status !== PENDING) return
  setTimeout(() => {
    let runFulfilled = () => {
      let cb;
      while (cb = this._fulfilledQueues.shift()) {
        cb(this._value)
      }
    }
    let runRejected = () => {
      let cb;
      while (cb = this._rejectedQueues.shift()) {
        cb(this._value)
      }
    }
    /* 如果resolve的参数为Promise对象,则必须等待该Promise对象状态改变后,
      当前Promsie的状态才会改变,且参数promise的状态和值会传递给当前Promsie对象
    */
    if (val instanceof Promise) {
      val.then(v => {
        this._value = v
        this._status = FULFILLED
        runFulfilled()
      }, e => {
        this._value = e
        this._status = REJECTED
        runRejected()
      })
    } else {
      this._value = val
      this._status = FULFILLED
      runFulfilled()
    }
  }, 0)
}
复制代码

到这里我们就把Promise的核心thenresolve给讲得差不多了。

Promise的其它方法

最后补充Promise类的其它方法,由于没有太复杂的逻辑,我就话不多说,直接上代码了。

class Promise {
    constructor (fn) {}
    then (onFulfilled, onRejected) {}
    catch (onRejected) {}
    _resolve (val) {}
    _reject (err) {}
    /**********************************/
    // 添加静态resolve方法
    static resolve (value) {
      // 如果参数是Promise实例,直接返回这个实例
      if (value instanceof Promise) return value
      return new Promise(resolve => resolve(value))
    }
    // 添加静态reject方法
    static reject (value) {
      return new Promise((resolve ,reject) => reject(value))
    }
    // 添加静态all方法
    static all (list) {
      return new Promise((resolve, reject) => {
        /* 返回值的集合 */
        let values = []
        let count = 0
        for (let [i, p] of list.entries()) {
          // 数组元素有可能不是Promise实例,所以先调用Promise.resolve包装成Promise实例
          Promise.resolve(p).then(res => {
            values[i] = res
            count++
            // 所有状态都变成fulfilled时返回的Promise状态就变成fulfilled
            if (count === list.length) resolve(values)
          }, err => {
            // 有一个被reject时返回的Promise状态就变成rejected
            reject(err)
          })
        }
      })
    }
    // 添加静态race方法
    static race (list) {
      return new Promise((resolve, reject) => {
        for (let p of list) {
          // 只要有一个实例率先改变状态,返回的Promise的状态就跟着改变
          Promise.resolve(p).then(res => {
            resolve(res)
          }, err => {
            reject(err)
          })
        }
      })
    }
    finally (cb) {
      return this.then(
        value  => Promise.resolve(cb()).then(() => value),
        reason => Promise.resolve(cb()).then(() => { throw reason })
      );
    }
}
复制代码

本文使用 mdnice 排版