背景

Promise是为了解决Javascript回调嵌套过多导致回调地狱(callbackhell)而产生的。目前已被纳入了es2015规范,主流浏览器都支持Promise。为了在工作中更好的运用Promise,我们需要理解Promise规范与内部实现机制,下面我们来手动实现一个Promise。

Promise/A+规范

在写代码之前让我们先了解下 Promise/A+规范。
一个promise有三种状态:

  • pending:表示初始状态,可以转移到 fullfilled 或者 rejected 状态
  • fulfilled:表示操作成功,不可转移状态
  • rejected:表示操作失败,不可转移状态
  • 必须有一个 then 异步执行方法,then 接受两个参数且必须返回一个promise:

Promise - 图1

借用这张来自MDN的流程图我们可以清晰的看到 Promise 状态的流转过程。

简单版

下面我们来实现一个简单版的 Promise:

  1. function Promise1(executor){
  2. let self = this;
  3. self.status = 'pending';
  4. self.value = undefined;
  5. self.reason = undefined;
  6. function resolve(value) {
  7. if(self.status==='pending'){
  8. self.status = 'fullfilled';
  9. self.value = value;
  10. }
  11. }
  12. function reject(reason) {
  13. if(self.status==='pending') {
  14. self.status = 'rejected';
  15. self.reason = reason;
  16. }
  17. }
  18. try{
  19. executor(resolve,reject);
  20. }catch(e) {
  21. reject(e);
  22. }
  23. }
  24. Promise1.prototype.then = function(onFullfilled,onRejected) {
  25. if(this.status==='fullfilled') {
  26. onFullfilled(this.value);
  27. }
  28. if(this.status==='rejected') {
  29. onRejected(this.reason);
  30. }
  31. }
  32. //测试
  33. let p= new Promise1(function(resolve,reject){resolve(1)});
  34. p.then(function(x){console.log(x)})
  35. //输出1

支持异步

现在,我们实现了最简单的 Promise。以上版本的Promise是存在很多问题的。为什么呢?最大的问题是它不支持异步,然而在现实中,Promise绝大多数使用场景都是异步。让我们来为 Promise 加入异步功能。

  1. const PENDING = 'pending';
  2. const FULFILLED = 'fullfilled';
  3. const REJECTED = 'rejected';
  4. function Promise1(executor){
  5. let self = this;
  6. self.status = PENDING;
  7. self.value = undefined;
  8. self.reason = undefined;
  9. self.fullfilledCallbacks = [];
  10. self.rejectedCallbacks = [];
  11. function resolve(value) {
  12. if(value instanceof Promise) {
  13. value.then(resolve,reject);
  14. }
  15. setTimeout(function(){
  16. if(self.status===PENDING){
  17. self.status = FULFILLED;
  18. self.value = value;
  19. self.fullfilledCallbacks.forEach(function(cb){
  20. cb(self.value)
  21. })
  22. }
  23. })
  24. }
  25. function reject(reason) {
  26. setTimeout(function(){
  27. if(self.status===PENDING) {
  28. self.status = REJECTED;
  29. self.reason = reason;
  30. self.rejectedCallbacks.forEach(function(cb){
  31. cb(self.reason);
  32. })
  33. }
  34. })
  35. }
  36. try{
  37. executor(resolve,reject);
  38. }catch(e) {
  39. reject(e);
  40. }
  41. }
  42. Promise1.prototype.then = function(onFulfilled,onRejected) {
  43. let self = this;
  44. return new Promise1(function(resolve,reject){
  45. function success(value) {
  46. let _value = (typeof onFulfilled === 'function' && onFulfilled(value)) || value;
  47. resolve(_value)
  48. }
  49. function error(reason) {
  50. let _reason = (typeof onRejected === 'function' && onRejected(reason)) || reason;
  51. reject(_reason);
  52. }
  53. if(self.status==PENDING) {
  54. self.fullfilledCallbacks.push(success);
  55. self.rejectedCallbacks.push(error);
  56. } else if(self.status==FULLFILLED){
  57. success.call(this,this.value)
  58. } else if(self.status==REJECTED) {
  59. error.call(this,this.reason);
  60. }
  61. })
  62. }

以上代码中,我们做了如下更改:

  1. 将 Promise 三个状态定义为常量,方便维护
  2. 对于 Promise resolve和reject 函数执行加入异步处理
  3. 在Promise.then中返回新的Promise对象,使Promise可以支持链式调用

错误处理以及静态方法

下面让我们来为Promise 添加错误处理以及静态方法:

  1. //错误处理
  2. Promise1.prototype.catch = function(onRejected) {
  3. return this.then(null, onRejected);
  4. }
  5. //返回fullfilled Promise对象
  6. Promise1.resolve = function (value) {
  7. return new Promise1(resolve => {
  8. resolve(value);
  9. });
  10. }
  11. //返回 rejected Promise 对象
  12. Promise1.reject = function (reason) {
  13. return new Promise1((resolve, reject) => {
  14. reject(reason);
  15. });
  16. }
  17. //Promise.all方法
  18. Promise1.all = function(promises) {
  19. function gen(length, resolve) {
  20. let count = 0;
  21. let values = [];
  22. return function(i, value) {
  23. values[i] = value;
  24. if (++count === length) {
  25. resolve(values);
  26. }
  27. }
  28. }
  29. return new Promise1((resolve, reject) => {
  30. let done = gen(promises.length, resolve);
  31. promises.forEach((promise, index) => {
  32. promise.then((value) => {
  33. done(index, value)
  34. }, reject)
  35. })
  36. })
  37. }
  38. //Promise.race方法
  39. Promise1.race = function(promises) {
  40. return new Promise1((resolve, reject) => {
  41. promises.forEach((promise, index) => {
  42. promise.then(resolve, reject);
  43. });
  44. });
  45. }

这里有个问题,就是在当我们console.log(Promise1.resolve(‘a’))的时候,我发现打印出来的状态竟然是 pending状态,我猜想原因是应该是resolve中函数异步执行,在当我们console的时候setTimeout中代码未执行,所以我给出的解决方法是将状态变化与赋值移到setTimeout外面,这样就不会产生刚才的问题了,更改后代码长这样:

function resolve(value) {
    if(value instanceof Promise) {
        value.then(resolve,reject);
    }
      self.status = FULFILLED;
      self.value = value;
      setTimeout(function(){
        if(self.status===PENDING){
        self.fullfilledCallbacks.forEach(function(cb){
            cb(self.value)
        })
      }
    })
  }

function reject(reason) {
    self.status = REJECTED;
    self.reason = reason;
      setTimeout(function(){
        if(self.status===PENDING) {
        self.rejectedCallbacks.forEach(function(cb){
            cb(self.reason);
        })
      }
    })
  }

总结

经过以上实践,我们成功的手写了一个功能完备的 Promise。这里给我的最大启发是如果我们想学习一个东西,必须深入到它的底层,了解它的运行原理与具体实现方法,并且可以造一个简单的轮子,这样才算我们掌握了该知识点。从前的我对于这一点没有关注的太多,导致在用某个知识点时只是掌握的它的表层用法,在高级一点的使用场景时完全不会运用。以后我会更加注重源码方面的学习,弥补我这方面的不足。