实现Promise

使用Promise是极好的,12bet,是如此有用以至于我觉得应该好好研究一下Promise,12博网址,甚至是实现一个简易的版本。12bet,实现之前,我们先来看看Promise的用途:

使用Promise

callback hell

Promise的第一个用途是能够很好地解决回调黑洞的问题,假设要实现一个用户展示的任务,12博网址,这个任务分为三步:

  1. 获取用户信息
  2. 获取用户图像
  3. 弹窗提示

不使用Promise,我们的实现可能是这样子:

getUserInfo(id, function (info) {  
  getUserImage(info.img, function () {
    showTip();
  })
})

这里只是三步,如果有更长串的任务时,我们就会陷入到回调黑洞之中,12bet,为了解决这个问题,我们就可以使用Promise来处理这一长串任务,12bet,使用Promise的版本是这样子的:

// getUserInfo返回promise
getUserInfo(id)  
  .then(getUserImage)
  .then(showTip)
  .catch(function (e) {
     console.log(e);
  });

原来向右发展的代码,开始向下发展,这样也更适合编程习惯,如果要让我们的代码更加健壮,我们就需要在每一步来处理错误信息,使用promise这后,我们只需要在最后的catch中做善后处理。

并发

假如我们要显示某一个页的10条记录,但是我们只有一个通过id获取记录的接口,这样我们就需要发送10个请求,并且所有请求都完成之后再将记录全部添加到页面之中,Promise在这个场景下使用是特别合适的。

代码可能是这样子:

// ids要获取信息的所有记录id
// getRecordById获取记录的接口,返回promise
Promise.all(ids.map(getRecordById))  
  .then(showRecords)
  .catch(function (e) {
     console.log(e);
  });

这就是Promise的一些简单的用途,当然令人兴奋的是Promise已经是ES6的标准,而且目前很多浏览器已经原生支持Promise了。对于那些无法使用Promise的浏览器,我们就只能自己去实现了,下面就来看看Promise的简单实现吧。

实现

warm up

先来盗用一张MDN的图,先来热热身,看看Promise的状态迁移: promise Promise有三种状态:

  1. pending:初始状态, 非 fulfilled 或 rejected
  2. fulfilled: 成功的操作
  3. rejected: 失败的操作

我们可以看出新建的Promise是pending状态,fulfill之后就会执行调用then的回调函数了,倘若reject了就会调用catch来进行异常处理了,并且无论是调用then还是catch都会返回新的promise,这就是为什么promise可以链式调用了。

接着,我们来研究一下规范是怎么描述 promise的。这里只抽取核心部分,边界问题不考虑。

构造函数:Promise ( executor )

  1. 检查参数:例如executor是不是函数啊
  2. 初始化:[[State]]=pending[[FulfillReactions]]=[],[[RejectReactions]]=[]
  3. 创建resolve对象:{[[Resolve]]: resolve, [[Reject]]: reject}
  4. 执行executor:executor(resolve, reject)

因此构造函数里面传入的excuter是立即被执行的。FulfillReactions存储着promise执行成功时要做的操作,RejectReactions存储着promise是要执行的操作。

function Promise(resolver) {  
  this._id = counter++;
  this._state = PENDING;
  this._result = undefined;
  this._subscribers = [];
  var promise = this;
  if (noop !== resolver) {
    try {
      resolver(function (value) {
        resolve(promise, value);
      }, function (reason) {
        reject(promise, reason);
      });
    } catch (e) {
      reject(promise, e);
    }
  }
}

FulfillPromise(promise, value)

  1. 检查[[state]],必须为pending(不是pending的表示已经解析,不能重复解析)
  2. 赋值:[[Result]]=value[[state]]=fulfilled
  3. 触发[[FulfillReactions]]的操作

和FulfillPromise联系最紧密的就是ResolvePromise了,这里我们给出的是ResolvePromise的实现,区别只是多了直接解析Promise。

function resolve(promise, value) {  
  // 要resolve的为promise(then的callback返回的是promise)
  if (typeof value === 'object'
    && promise.constructor === value.constructor) {
    handleOwnThenable(promise, value);
  } 
  // 要resolve的是值
  else {
    if (promise._state !== PENDING) { return; }
    promise._result = value;
    promise._state = FULFILLED;
    asap(publish, promise);
  }
}
function handleOwnThenable(promise, thenable) {  
  // 如果返回的promise已经完成
  // 直接用该promise的值resolve父promise
  if (thenable._state === FULFILLED) {
    resolve(promise, thenable._result);
  } else if (thenable._state === REJECTED) {
    reject(promise, thenable._result);
  }
  // 如果返回的promise未完成
  // 要等该promise完成再resolve父promise
  else {
    subscribe(thenable, undefined, function(value) {
      resolve(promise, value);
    }, function(reason) {
      reject(promise, reason);
    });
  }
}

RejectPromise(promise, reason)

  1. 检查[[state]],必须为pending(不是pending的表示已经解析,不能重复解析)
  2. 赋值:[[Result]]=reason[[state]]=rejected
  3. 触发[[RejectReactions]]的操作

触发[[FulfillReactions]]和触发[[RejectReactions]]实际就是遍历数组,执行所有的回调函数。

function reject(promise, reason) {  
  if (promise._state !== PENDING) { return; }
  promise._state = REJECTED;
  promise._result = reason;
  asap(publish, promise);
}

Promise.prototype.then(onFullfilled, onRejected)

  1. promise=this
  2. 新建resultCapability三元组,{[[Promise]], [[Resolve]], [[Reject]]}([[Promise]]新建的)
  3. fulfillReaction={[[Capabilities]]: resultCapability, [[Handler]]: onFulfilled}
  4. rejectReaction={[[Capabilities]]: resultCapability, [[Handler]]: onRejected}
  5. 如果[[state]]是pending:fulfillReaction加入[[FulfillReactions]],rejectReaction加入[[RejectReactions]]
  6. 如果[[state]]是fulfilled:fulfillReaction加入执行队列
  7. 如果[[state]]是rejected:rejectReaction加入执行队列
  8. 返回resultCapability.[[Promise]]

这里可以看出构造函数和then的关系是很紧密的,新建的promise如果是异步操作,那么状态就是pending,调用then时会新建子promise,并且将回调操作加入父promise的[[FulfillReactions]]或[[RejectReactions]]的数组里,这实际就是发布订阅模式

他们是这样的关系: