leoyuan.github.io icon indicating copy to clipboard operation
leoyuan.github.io copied to clipboard

Promise模式参考实现

Open LeoYuan opened this issue 11 years ago • 6 comments

Promise模式是什么?

在JavaScript异步编程中,基于语言本身的事件驱动性,很容易出现异步回调层层嵌套的情况,如著名的The Pyramid of Doom,如下代码所示:

getUserName('userid', function(userName) {
  getUser(username, function(user) {
    authenticate(user, function(pwd) {
      if (pwd === 'password') {
        // do something
      }
    });
  });
});

假如写十层嵌套,上边的代码会变成什么样子? 注:上边的代码未进行异常处理,因为我不忍下笔,写出来的代码实在看不下去,眼花啊~

Promise模式主要是为了解决JavaScript在异步编程下代码结构不清晰的问题,具体思路是一个异步函数返回一个"承诺的"对象,可以用该对象进行事件监听,包括fulfill / reject / progress三个事件。

  • fulfill代表承诺的对象被正常返回了,如XMLHttpRequest对象的onload事件被触发了;
  • reject代表承诺的对象发生异常了,如XMLHttpRequest对象的onerror事件被触发了;
  • progress代表承诺的对象正在变化,常用场景是文件上传过程中,上传进度的不断变化。

如上述代码转换成Promise模式后可改写成: 注:原生版代码未处理异常分支,这里补上。

getUserName('userid').then(function(username) {
  return getUser(username);
}, function(err) {
  console.log('failed to invoke getUserName');
}).then(function(user) {
  return authenticate(user);
}, function(err) {
  console.log('failed to invoke getUser');
}).then(function(pwd) {
  if (pwd === 'password') {
    // do something
  }
}, function(err) {
  console.log('failed to invoke authenticate');
})

怎么样?代码是不是清晰很多?对于多层级嵌套,只需要遵循Promise规范,一直用then函数来注册事件监听就好了,对于需要同时完成的任务一、任务二、任务三,还可以通过when函数来处理。

Promise参考实现

第一步,实现最基础的Promise对象状态变更以及then监听事件

Promise对象有三种状态fulfilled / rejected / unfulfilled,状态可以由unfulfilled转为任意一种状态,对应的事件即为fulfill / reject / progress,状态一旦被转为fulfilled / rejected,则不能再转成其他状态。 示例代码如下:

/*
  promise模式的参考实现
 */
define(function (require, exports, module) {
  function Promise() {
    this.eventHandlers = {};
  }

  /**
   * 注册promise对象的fulfill/reject/progress事件发生时的回调函数
   * @param  function onFulfill  fulfill事件发生时的回调
   * @param  function onReject   onReject事件发生时的回调
   * @param  function onProgress onProgress事件发生时的回调
   * @return Promise  promise    当前promise对象,for chaining's sake.
   */
  Promise.prototype.then = function(onFulfill, onReject, onProgress) {
    if (isFunction(onFulfill)) {
      this.eventHandlers.onFulfill = onFulfill;
    }

    if (isFunction(onReject)) {
      this.eventHandlers.onReject = onReject;
    }

    if (isFunction(onProgress)) {
      this.eventHandlers.onProgress = onProgress;
    }

    return this;
  }


  /**
   * 将promise对象转换至fulfilled状态的函数
   * @param  any value [description]
   */
  Promise.prototype.fulfill = function() {
    if (this.eventHandlers.onFulfill) {
       this.eventHandlers.onFulfill(value);
    }
  }

  /**
   * 将promise对象转换至rejected状态的函数
   * @param  any err [description]
   */
  Promise.prototype.reject = function(err) {
    if (this.eventHandlers.onReject) {
      this.eventHandlers.onReject(err);
    }
  }

  /**
   * 将promise对象转换至progress状态的函数
   * @param  any value [description]
   */
  Promise.prototype.notify = function(value) {
    if (this.eventHandlers.onProgress) {
      this.eventHandlers.onProgress(value);
    }
  }

  return Promise;
});

使用例子

define(function (require, exports, module) {
  var Promise = require('./promise');

  function getCalculatePromise() {
    var promise = new Promise();
    setTimeout(function() {
      var rand = Math.random();
      if (rand > 0.8) {
        promise.reject('from getCalculatePromise, the result is greater than 0.8');
      } else {
        promise.fulfill(rand);
      }
    }, 1000);
    return promise;
  }

  function getAnotherCalculatePromise() {
    var promise = new Promise();
    setTimeout(function() {
      var rand = Math.random();
      if (rand > 0.7) {
        promise.reject('from getAnotherCalculatePromise, the result is greater than 0.7');
      } else {
        promise.fulfill(rand);
      }
    }, 1000);
    return promise;
  }

  function testNestedPromise() {
    display('testNestedPromise', { header: true });
    getCalculatePromise().then(function(result) {
      display('the result is ' + result);
      getAnotherCalculatePromise().then(function(result) {
        display('the result is ' + result);
      }, function(err) {
        display('error message: ' + err);
      })
    }, function(err) {
      display('error message: ' + err);
    });  
  }
});

这时已经完成一个最简单的Promise实现了。

第二步,实现Promise对象的pipeline/chaining操作

重点在于当第一个Promise对象的fulfill回调返回一个新的Promise对象时,需要将第二个then指定的事件绑定上去。 于是对上边的thenfulfill函数的实现做如下更改

Promise.prototype.then = function(onFulfill, onReject, onProgress) {
  if (!this.hasEventHandlers) {
    if (isFunction(onFulfill)) {
      this.eventHandlers.onFulfill = onFulfill;
    }

    if (isFunction(onReject)) {
      this.eventHandlers.onReject = onReject;
    }

    if (isFunction(onProgress)) {
      this.eventHandlers.onProgress = onProgress;
    }
    this.hasEventHandlers = true;
  } else {
    this.pendingHandlers.push(arguments);
  }

  return this;
}

Promise.prototype.fulfill = function() {
  var retPromise, retHandlers;
  if (this.eventHandlers.onFulfill) {
    retPromise = this.eventHandlers.onFulfill.apply(null, arguments);
    if (retPromise instanceof Promise) {
      retHandlers = this.pendingHandlers.shift();
      retPromise.then(retHandlers[0], retHandlers[1], retHandlers[2]);
    }
  }
}

第三步,实现Promise对象控制函数when

when函数主要用于等待多个Promise对象的执行,若其中有一个触发reject事件,则走入整体的error分支 具体用法如下

Promise.when(getCalculatePromise(), getAnotherCalculatePromise())
  .then(function(c1, c2) {
    display('the sum of c1 and c2 is ' + (c1 + c2));
  }, function(err) {
    display('error message: ' + err);
  });

实现思路:之前Promise对象在调用fulfill / reject方法时,没有通知其他模块的机制,在实现when方法时,一定得有某种事件机制,从而通知when控制的Promise对象的状态如何,从而做出相应的反应,于是写了简单的emit / on机制,代码如下:

Promise.prototype._emit = function(eventName) {
  if (this._innerHandlers[eventName]) {
    this._innerHandlers[eventName].apply(null, arraySlice(arguments, 1));
  }
}

Promise.prototype._on = function(eventName, callback) {
  // only support one handler
  this._innerHandlers[eventName] = callback;
}

并在fulfill / reject实现中分别加入this._emit('fulfill') / this._emit('reject')代码。 when代码如下:

/**
 * 等待所有promise对象完成fulfilled状态转换,执行fulfill回调,否则执行reject回调
 * @param  Promise promise1 [description]
 * ...
 * ...
 * @param  Promise promiseN [description]
 * @return {[type]}          [description]
 */
Promise.when = function(promise1, promise2, promise3) {
  var combinedPromise = new Promise();
  var promiseList = arraySlice(arguments);
  var promise, args = [], count = 0;
  for (var i=0, leni=promiseList.length; i<leni; i++) {
    promiseList[i]._on('fulfill', createCallback(i));
    promiseList[i]._on('reject', createRejectCallback());
  }

  function createCallback(index) {
    return function(value) {
      args[index] = value;
      count++;
      if (count === promiseList.length) {
        combinedPromise.fulfill.apply(combinedPromise, flattenArray(args));
      }
    };
  }

  function createRejectCallback() {
    return function(err) {
      if (combinedPromise.reject) {
        combinedPromise.reject.call(combinedPromise, err);
        // only invoke once
        combinedPromise.reject = null;
      }
    }
  }
  return combinedPromise;
}

到此,整个Promise参考实现就完成了,全部代码请参见Promise参考实现

参考资料

http://www.infoq.com/cn/news/2011/09/js-promise http://wiki.commonjs.org/wiki/Promises/A https://github.com/kriskowal/q/blob/master/q.js

LeoYuan avatar Jul 02 '13 03:07 LeoYuan

@h63542 @wuguixiong @varfunction @Quchuanpeng @dafeizizhu 各位有空可以看看,提提宝贵意见。

LeoYuan avatar Jul 02 '13 03:07 LeoYuan

fulfill是你自己发明的么? 和resolve 啥区别? @dafeizizhu

h63542 avatar Jul 03 '13 02:07 h63542

这两个词应该是同义词,在jQuery的Deferred对象的实现中,比较偏向于用resolve,而在其他地方,比如q.js,比较偏向于用fulfill,我个人比较喜欢fulfill这个词。 而且在Promises/A+规范中,强制使用fulfill,而不是resolve

Promise terminology: specifically, Promises/A+ standardizes on the terms “fulfilled”, “rejected”, “reason”, and “pending”.

LeoYuan avatar Jul 03 '13 04:07 LeoYuan

@LeoYuan 小问题:这里的return有嘛用?

getUserName('userid').then(function(username) {
  return getUser(username);
}, function(err) {
  console.log('failed to invoke getUserName');
})

dafeizizhu avatar Jul 03 '13 15:07 dafeizizhu

我们来详细讨论一下问题哈。。。 Promises/A这个网站挂了,就只能看看Promises/A+了,其中对then函数的规范是

then must return a promise 4.2.

promise2 = promise1.then(onFulfilled, onRejected);

If either onFulfilled or onRejected returns a value that is not a promise, promise2 must be fulfilled with that value.

If either onFulfilled or onRejected throws an exception, promise2 must be rejected with the thrown exception as the reason.

If either onFulfilled or onRejected returns a promise (call it returnedPromise), promise2 must assume the state of returnedPromise 4.3:

If returnedPromise is pending, promise2 must remain pending until returnedPromise is fulfilled or rejected. If/when returnedPromise is fulfilled, promise2 must be fulfilled with the same value. If/when returnedPromise is rejected, promise2 must be rejected with the same reason.

重点在最后几句,当从onFulfilled / onRejected中返回promise对象时,假设为returnedPromise,该promise对象需要替换掉上边的promise2对象,并且你需要将原本链式写法中的第二个then写的事件绑定上去。

问题来了,如何从onFulfilled / onRejected判断是否返回了promise对象,当然需要用到return才能让规范实现者知道啦?对不?

LeoYuan avatar Jul 04 '13 02:07 LeoYuan

@LeoYuan 我错了,眼误……原谅哥……

dafeizizhu avatar Jul 04 '13 06:07 dafeizizhu