Promise探讨

1、前言

你们都知道JavaScript一大特色就是单线程,为了避免阻塞主线程,有些耗时操做(好比ajax)必须放在任务队列中异步执行。传统的异步编程解决方案之一回调,很容易产生臭名昭著的回调地狱问题。html

fs.readdir(source, function(err, files) {
  if (err) {
    console.log('Error finding files: ' + err)
  } else {
    files.forEach(function(filename, fileIndex) {
      console.log(filename)
      gm(source + filename).size(function(err, values) {
        if (err) {
          console.log('Error identifying file size: ' + err)
        } else {
          console.log(filename + ' : ' + values)
          aspect = (values.width / values.height)
          widths.forEach(function(width, widthIndex) {
            height = Math.round(width / aspect)
            console.log('resizing ' + filename + 'to ' + height + 'x' + height)
            this.resize(width, height).write(dest + 'w' + width + '_' + filename, function(err) {
              if (err) console.log('Error writing file: ' + err)
            })
          }.bind(this))
        }
      })
    })
  }
})

虽然回调地狱能够经过减小嵌套、模块化等方式来解决,但咱们有更好的方案能够采起,那就是 Promisegit

2、含义

Promise 是一个对象,保存着异步操做的结果,在异步操做结束后,会变动 Promise 的状态,而后调用注册在 then 方法上回调函数。 ES6 原生提供了 Promise 对象,统一用法(具体可参考阮一峰的ES6入门es6

3、实现

Promise 的使用想必你们都很熟练,但是究其内部原理,在这以前,我一直是只知其一;不知其二。本着知其然,也要知其因此然的目的,开始对 Promise 的实现产生了兴趣。github

众所周知,Promise 是对 Promises/A+ 规范的一种实现,那咱们首先得了解规范,
详情请看Promise/A+规范,我的github上有对应的中文翻译README.mdajax

promise构造函数

规范没有指明如何书写构造函数,那就参考下 ES6 的构造方式编程

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操做成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

Promise 构造函数接受一个函数做为参数,该函数的两个参数分别是 resolvereject数组

resolve 函数的做用是将 Promise 对象的状态从 pending 变为 fulfilled ,在异步操做成功时调用,并将异步操做的结果,做为参数传递给注册在 then 方法上的回调函数(then方法的第一个参数); reject 函数的做用是将 Promise 对象的状态从 pending 变为 rejected ,在异步操做失败时调用,并将异步操做报出的错误,做为参数传递给注册在 then 方法上的回调函数(then方法的第二个参数)promise

因此咱们要实现的 promise (小写以便区分ES6的Promise )构造函数大致以下:浏览器

// promise 构造函数
function promise(fn) {
  let that = this
  that.status = 'pending' // 存储promise的state
  that.value = '' // 存储promise的value
  that.reason = '' // 存储promise的reason
  that.onFulfilledCb = [] // 存储then方法中注册的回调函数(第一个参数)
  that.onRejectedCb = [] // 存储then方法中注册的回调函数(第二个参数)

  // 2.1
  function resolve(value) {
    // 将promise的状态从pending更改成fulfilled,而且以value为参数依次调用then方法中注册的回调
    setTimeout(() => {
      if (that.status === 'pending') {
        that.status = 'fulfilled'
        that.value = value
        // 2.2.二、2.2.6
        that.onFulfilledCb.map(item => {
          item(that.value)
        })
      }
    }, 0)
  }

  function reject(reason) {
    // 将promise的状态从pending更改成rejected,而且以reason为参数依次调用then方法中注册的回调
    setTimeout(() => {
      if (that.status === 'pending') {
        that.status = 'rejected'
        that.reason = reason
        // 2.2.三、2.2.6
        that.onRejectedCb.map(item => {
          item(that.reason)
        })
      }
    }, 0)
  }

  fn(resolve, reject)
}

规范2.2.6中明确指明 then 方法能够被同一个 promise 对象调用,因此这里须要用一个数组 onFulfilledCb 来存储then方法中注册的回调异步

这里咱们执行 resolve reject 内部代码使用setTimeout,是为了确保 then 方法上注册的回调能异步执行(规范3.1)

then方法

promise 实例具备 then 方法,也就是说,then 方法是定义在原型对象 promise.prototype 上的。它的做用是为 promise 实例添加状态改变时的回调函数。

规范2.2promise 必须提供一个 then 方法 promise.then(onFulfilled, onRejected)
规范2.2.7 then 方法必须返回一个新的promise

阅读理解规范2.1和2.2,咱们也很容易对then方法进行实现:

promise.prototype.then = function(onFulfilled, onRejected) {
  let that = this
  let promise2

  // 2.2.一、2.2.5
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
  onRejected = typeof onRejected === 'function' ? onRejected : r => r

  if (that.status === 'pending') {
    // 2.2.7
    return promise2 = new promise((resolve, reject) => {
      that.onFulfilledCb.push(value => {
        try {
          let x = onFulfilled(value)
        } catch(e) {
          // 2.2.7.2
          reject(e)
        }
      })

      that.onRejectedCb.push(reason => {
        try {
          let x = onRejected(reason)
        } catch(e) {
          // 2.2.7.2
          reject(e)
        }
      })
    })
  }
}

重点在于对 onFulfilledonRejected 函数的返回值x如何处理,规范中提到一个概念叫
Promise Resolution Procedure ,这里咱们就叫作Promise解决过程

Promise 解决过程是一个抽象的操做,须要输入一个 promise 和一个值,咱们表示为 [[Resolve]](promise, x),若是 xthen 方法且看上去像一个 Promise ,解决程序即尝试使 promise 接受 x 的状态;不然用 x 的值来执行 promise

promise解决过程

对照规范2.3,咱们再来实现 promise resolutionpromise resolution 针对x的类型作了各类处理:若是 promisex 指向同一对象,以 TypeErrorreason 拒绝执行 promise、若是 xpromise ,则使 promise 接受 x 的状态、若是 x 为对象或者函数,判断 x.then 是不是函数、 若是 x 不为对象或者函数,以 x 为参数执行 promise(resolve和reject参数携带promise2的做用域,方便在x状态变动后去更改promise2的状态)

// promise resolution
function promiseResolution(promise2, x, resolve, reject) {
  let then
  let thenCalled = false
  // 2.3.1
  if (promise2 === x) {
    return reject(new TypeError('promise2 === x is not allowed'))
  }
  // 2.3.2
  if (x instanceof promise) {
    x.then(resolve, reject)
  }
  // 2.3.3
  if (typeof x === 'object' || typeof x === 'function') {
    try {
      // 2.3.3.1
      then = x.then
      if (typeof then === 'function') {
        // 2.3.3.2
        then.call(x, function resolvePromise(y) {
          // 2.3.3.3.3
          if (thenCalled) return
          thenCalled = true
          // 2.3.3.3.1
          return promiseResolution(promise2, y, resolve, reject)
        }, function rejectPromise(r) {
          // 2.3.3.3.3
          if (thenCalled) return
          thenCalled = true
          // 2.3.3.3.2
          return reject(r)
        })
      } else {
        // 2.3.3.4
        resolve(x)
      }
    } catch(e) {
      // 2.3.3.3.4.1
      if (thenCalled) return
      thenCalled = true
      // 2.3.3.2
      reject(e)
    }
  } else {
    // 2.3.4
    resolve(x)
  }
}

完整代码可查看stage-4

思考

以上,基本实现了一个简易版的 promise ,说白了,就是对 Promises/A+ 规范的一个翻译,将规范翻译成代码。由于你们的实现都是基于这个规范,因此不一样的 promise 实现之间可以共存(不得不说制定规范的人才是最厉害的)

function doSomething() {
  return new promise((resolve, reject) => {
    setTimeout(() => {
      resolve('promise done')
    }, 2000)
  })
}

function doSomethingElse() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject('ES6 promise')
    }, 1000)
  })
}

this.promise2 = doSomething().then(doSomethingElse)
console.log(this.promise2)

至于 ES6finallyall 等经常使用方法,规范虽然没有制定,可是借助 then 方法,咱们实现起来也很方便stage-5

ES7Async/Await 也是基于 promise 来实现的,能够理解成 async 函数会隐式地返回一个 Promiseawait 后面的执行代码放到 then 方法中

更深层次的思考,你须要理解规范中每一条制定的意义,好比为何then方法不像jQuery那样返回this而是要从新返回一个新的promise对象(若是then返回了this,那么promise2就和promise1的状态同步,promise1状态变动后,promise2就没办法接受后面异步操做进行的状态变动)、 promise解决过程 中为何要规定 promise2x 不能指向同一对象(防止循环引用)

promise的弊端

promise完全解决了callback hell,但也存在如下一些问题

  1. 延时问题(涉及到evnet loop)
  2. promise一旦建立,没法取消
  3. pending状态的时候,没法得知进展到哪一步(好比接口超时,能够借助race方法)
  4. promise会吞掉内部抛出的错误,不会反映到外部。若是最后一个then方法里出现错误,没法发现。(能够采起hack形式,在promise构造函数中判断onRejectedCb的数组长度,若是为0,就是没有注册回调,这个时候就抛出错误,某些库实现done方法,它不会返回一个promise对象,且在done()中未经处理的异常不会被promise实例所捕获)
  5. then方法每次调用都会建立一个新的promise对象,必定程度上形成了内存的浪费

    总结

    支持 promise 的库有不少,如今主流的浏览器也都原生支持 promise 了,并且还有更好用的 Async/Await 。之因此还要花精力去写这篇文章,道理很简单,就是想对规范有一个更深的理解,但愿看到这里的同窗一样也能有所收获

相关文章
相关标签/搜索