从0到1实现Promise

栏目: JavaScript · 发布时间: 6年前

内容简介:Promise大家一定都不陌生了,JavaScript异步流程从最初的Callback,到Promise,到Generator,再到目前使用最多的Async/Await(如果对于这些不熟悉的可以参考我另一篇文章《JavaScript异步编程》),这不仅仅是技术实现的发展,更是思想上对于如何控制异步的递进。Promise作为后续方案的基础,是重中之重,也是面试时候最常被问到的。今天我们就一起从0到1实现一个基于A+规范的Promise,过程中也会对Promise的异常处理,以及是否可手动终止做一些讨论,最后

Promise大家一定都不陌生了,JavaScript异步流程从最初的Callback,到Promise,到Generator,再到目前使用最多的Async/Await(如果对于这些不熟悉的可以参考我另一篇文章《JavaScript异步编程》),这不仅仅是技术实现的发展,更是思想上对于如何控制异步的递进。Promise作为后续方案的基础,是重中之重,也是面试时候最常被问到的。

今天我们就一起从0到1实现一个基于A+规范的Promise,过程中也会对Promise的异常处理,以及是否可手动终止做一些讨论,最后会对我们实现的Promise做单元测试。完整的代码已经上传到github,想直接看代码的可以 点这里

虽然已经有很多带你实现Promise类的文章了,但每个人理解的程度不一样,也许不同的文章可以带给你不同的思考呢,那我们就开始吧。

正文

1. 基础框架

new Promise()时接收一个executor函数作为参数,该函数会立即执行,函数中有两个参数,它们也是函数,分别是resolve和reject,函数同步执行一定要放在try...catch中,否则无法进行错误捕获。

MyPromise.js

function MyPromise(executor) {

  function resolve(value) {

  }

  function reject(reason) {
    
  }

  try {
    executor(resolve, reject);
  } catch (reason) {
    reject(reason);
  }
}

module.exports = MyPromise;
复制代码

resolve()接收Promise成功值value,reject接收Promise失败原因reason。

test.js

let MyPromise = require('./MyPromise.js');

let promise = new MyPromise(function(resolve, reject) {
  resolve(123);
})
复制代码

2. 添加状态机

目前实现存在的问题:

  1. Promise是一个状态机的机制,初始状态为 pending ,成功状态为 fulfilled ,失败状态为 rejected 。只能从 pending -> fulfilled ,或者从 pending -> rejected ,并且状态一旦转变,就永远不会再变了。

所以,我们需要为Promise添加一个状态流转的机制。

MyPromise.js

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

function MyPromise(executor) {
  let self = this;
  self.state = PENDING;


  function resolve(value) {
    if (self.state === PENDING) {
      self.state = FULFILLED;
    }
  }

  function reject(reason) {
    if (self.state === PENDING) {
      self.state = REJECTED;
    }
  }

  try {
    executor(resolve, reject);
  } catch (reason) {
    reject(reason);
  }
}

module.exports = MyPromise;
复制代码

test.js

let MyPromise = require('./MyPromise.js');

let promise = new MyPromise(function(resolve, reject) {
  resolve(123);
});

promise.then(function(value) {
  console.log('value', value);
}, function(reason) {
  console.log('reason', reason);
})
复制代码

3. 添加 then 方法

Promise拥有一个 then 方法,接收两个函数 onFulfilledonRejected ,分别作为Promise成功和失败的回调。所以,在 then 方法中我们需要对状态 state 进行判断,如果是 fulfilled ,则执行 onFulfilled(value) 方法,如果是 rejected ,则执行 onRejected(reason) 方法。

由于成功值 value 和失败原因 reason 是由用户在 executor 中通过 resolve(value)reject(reason) 传入的,所以我们需要有一个全局的 valuereason 供后续方法获取。

MyPromise.js

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

function MyPromise(executor) {
  let self = this;

  self.state = PENDING;
  self.value = null;
  self.reason = null;

  function resolve(value) {
    if (self.state === PENDING) {
      self.state = FULFILLED;
      self.value = value;
    }
  }

  function reject(reason) {
    if (self.state === PENDING) {
      self.state = REJECTED;
      self.reason = reason;
    }
  }

  try {
    executor(resolve, reject);
  } catch (reason) {
    reject(reason);
  }
}

MyPromise.prototype.then = function(onFuifilled, onRejected) {
  let self = this;

  if (self.state === FULFILLED) {
    onFuifilled(self.value);
  }

  if (self.state === REJECTED) {
    onRejected(self.reason);
  }
};

module.exports = MyPromise;
复制代码

4. 实现异步调用resolve

目前实现存在的问题:

  1. 同步调用 resolve() 没有问题,但如果是异步调用,比如放到 setTimeout 中,因为目前的代码在调用 then() 方法时, state 仍是 pending 状态,当timer到时候调用 resolve()state 修改为 fulfilled 状态,但是 onFulfilled() 函数已经没有时机调用了。

针对上述问题,进行如下修改:

MyPromise.js

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

function MyPromise(executor) {
  let self = this;

  self.state = PENDING;
  self.value = null;
  self.reason = null;
  self.onFulfilledCallbacks = [];
  self.onRejectedCallbacks = [];

  function resolve(value) {
    if (self.state === PENDING) {
      self.state = FULFILLED;
      self.value = value;

      self.onFulfilledCallbacks.forEach(function(fulfilledCallback) {
        fulfilledCallback();
      });
    }
  }

  function reject(reason) {
    if (self.state === PENDING) {
      self.state = REJECTED;
      self.reason = reason;

      self.onRejectedCallbacks.forEach(function(rejectedCallback) {
        rejectedCallback();
      });
    }
  }

  try {
    executor(resolve, reject);
  } catch (reason) {
    reject(reason);
  }
}

MyPromise.prototype.then = function(onFuifilled, onRejected) {
  let self = this;

  if (self.state === PENDING) {
    self.onFulfilledCallbacks.push(() => {
        onFuifilled(self.value);
    });
    self.onRejectedCallbacks.push(() => {
        onRejected(self.reason);
    });
  }

  if (self.state === FULFILLED) {
    onFuifilled(self.value);
  }

  if (self.state === REJECTED) {
    onRejected(self.reason);
  }
};

module.exports = MyPromise;
复制代码

我们添加了两个回调函数数组 onFulfilledCallbacksonRejectedCallbacks ,用来存储 then() 方法中传入的成功和失败回调。然后,当用户调用 resolve()reject() 的时候,修改 state 状态,并从相应的回调数组中依次取出回调函数执行。

同时,通过这种方式我们也实现了可以注册多个 then() 函数,并且在成功或者失败时按照注册顺序依次执行。

test.js

let MyPromise = require('./MyPromise.js');

let promise = new MyPromise(function(resolve, reject) {
  setTimeout(function() {
    resolve(123);
  }, 1000);
});

promise.then(function(value) {
  console.log('value1', value);
}, function(reason) {
  console.log('reason1', reason);
});

promise.then(function(value) {
  console.log('value2', value);
}, function(reason) {
  console.log('reason2', reason);
});
复制代码

5. then返回的仍是Promise

读过PromiseA+规范的同学肯定知道, then() 方法返回的仍是一个Promise,并且返回Promise的 resolve 的值是上一个Promise的 onFulfilled() 函数或 onRejected() 函数的返回值。如果在上一个Promise的 then() 方法回调函数的执行过程中发生了错误,那么会将其捕获到,并作为返回的Promise的 onRejected 函数的参数传入。比如:

let promise = new Promise((resolve, reject) => {
  resolve(123);
});

promise.then((value) => {
  console.log('value1', value);
  return 456;
}).then((value) => {
  console.log('value2', value);
});

let promise = new Promise((resolve, reject) => {
  resolve(123);
});
复制代码

打印结果为:

value1 123  value2 456
let promise = new Promise((resolve, reject) => {
  resolve(123);
});

promise.then((value) => {
  console.log('value1', value);
  a.b = 2;    // 这里存在语法错误
  return 456;
}).then((value) => {
  console.log('value2', value);
}, (reason) => {
  console.log('reason2', reason);
});
复制代码

打印结果为:

value1 123  reason2 ReferenceError: a is not defined

可以看到, then() 方法回调函数如果发生错误,会被捕获到,那么 then() 返回的Promise会自动变为 onRejected ,执行 onRejected() 回调函数。

let promise = new Promise((resolve, reject) => {
  reject(123);
});

promise.then((value) => {
  console.log('value1', value);
  return 456;
}, (reason) => {
  console.log('reason1', reason);
  return 456;
}).then((value) => {
  console.log('value2', value);
}, (reason) => {
  console.log('reason2', reason);
});
复制代码

打印结果为:

reason1 123  value2 456

好啦,接下来我们就去实现 then() 方法依然返回一个Promise。

MyPromise.js

MyPromise.prototype.then = function(onFuifilled, onRejected) {
  let self = this;
  let promise2 = null;

  promise2 = new MyPromise((resolve, reject) => {
    if (self.state === PENDING) {
      self.onFulfilledCallbacks.push(() => {
        try {
          let x = onFuifilled(self.value);
          self.resolvePromise(promise2, x, resolve, reject);
        } catch(reason) {
          reject(reason);
        }
      });
      self.onRejectedCallbacks.push(() => {
        try {
          let x = onRejected(self.reason);
          self.resolvePromise(promise2, x, resolve, reject);
        } catch(reason) {
          reject(reason);
        }
      });
    }
  
    if (self.state === FULFILLED) {
      try {
        let x = onFuifilled(self.value);
        self.resolvePromise(promise2, x, resolve, reject);
      } catch (reason) {
        reject(reason);
      }
    }
  
    if (self.state === REJECTED) {
      try {
        let x = onRejected(self.reason);
        self.resolvePromise(promise2, x, resolve, reject);
      } catch (reason) {
        reject(reason);
      }
    }
  });

  return promise2;
};
复制代码

可以看到,我们新增了一个 promise2 作为 then() 方法的返回值。通过 let x = onFuifilled(self.value) 或者 let x = onRejected(self.reason) 拿到 then() 方法回调函数的返回值,然后调用 self.resolvePromise(promise2, x, resolve, reject) ,将新增的 promise2xpromise2resolvereject 传入到 resolvePromise() 中。

所以,下面我们重点看一下 resolvePromise() 方法。

MyPromise.js

MyPromise.prototype.resolvePromise = function(promise2, x, resolve, reject) {
  let self = this;
  let called = false;   // called 防止多次调用

  if (promise2 === x) {
    return reject(new TypeError('循环引用'));
  }

  if (x !== null && (Object.prototype.toString.call(x) === '[object Object]' || Object.prototype.toString.call(x) === '[object Function]')) {
    // x是对象或者函数
    try {
      let then = x.then;

      if (typeof then === 'function') {
        then.call(x, (y) => {
          // 别人的Promise的then方法可能设置了getter等,使用called防止多次调用then方法
          if (called) return ;
          called = true;
          // 成功值y有可能还是promise或者是具有then方法等,再次resolvePromise,直到成功值为基本类型或者非thenable
          self.resolvePromise(promise2, y, resolve, reject);
        }, (reason) => {
          if (called) return ;
          called = true;
          reject(reason);
        });
      } else {
        if (called) return ;
        called = true;
        resolve(x);
      }
    } catch (reason) {
      if (called) return ;
      called = true;
      reject(reason);
    }
  } else {
    // x是普通值,直接resolve
    resolve(x);
  }
};
复制代码

resolvePromise() 是用来解析 then() 回调函数中返回的仍是一个 Promise ,这个 Promise 有可能是我们自己的,有可能是别的库实现的,也有可能是一个具有 then() 方法的对象,所以这里靠 resolvePromise() 来实现统一处理。

下面是翻译自PromiseA+规范关于 resolvePromise() 的要求:

Promise 解决过程

Promise 解决过程是一个抽象的操作,其需输入一个 promise 和一个值,我们表示为 [[Resolve]](promise, x),如果 x 有 then 方法且看上去像一个 Promise ,解决程序即尝试使 promise 接受 x 的状态;否则其用 x 的值来执行 promise 。

这种 thenable 的特性使得 Promise 的实现更具有通用性:只要其暴露出一个遵循 Promise/A+ 协议的 then 方法即可;这同时也使遵循 Promise/A+ 规范的实现可以与那些不太规范但可用的实现能良好共存。

运行 [[Resolve]](promise, x) 需遵循以下步骤:

  • x 与 promise 相等

    如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise

  • x 为 Promise

    如果 x 为 Promise ,则使 promise 接受 x 的状态:

    • 如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝
    • 如果 x 处于执行态,用相同的值执行 promise
    • 如果 x 处于拒绝态,用相同的据因拒绝 promise
  • x 为对象或函数 如果 x 为对象或者函数:

    • 把 x.then 赋值给 then
    • 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
    • 如果 then 是函数,将 x 作为函数的作用域 this 调用之。传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise:
      • 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
      • 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
      • 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
      • 如果调用 then 方法抛出了异常 e:
        • 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
        • 否则以 e 为据因拒绝 promise
      • 如果 then 不是函数,以 x 为参数执行 promise
    • 如果 x 不为对象或者函数,以 x 为参数执行 promise

如果一个 promise 被一个循环的 thenable 链中的对象解决,而 [[Resolve]](promise, thenable) 的递归性质又使得其被再次调用,根据上述的算法将会陷入无限递归之中。算法虽不强制要求,但也鼓励施者检测这样的递归是否存在,若检测到存在则以一个可识别的 TypeError 为据因来拒绝 promise。

参考上述规范,结合代码中的注释,相信大家可以理解 resolvePromise() 的作用了。

测试:

test.js

let MyPromise = require('./MyPromise.js');

let promise = new MyPromise(function(resolve, reject) {
  setTimeout(function() {
    resolve(123);
  }, 1000);
});

promise.then((value) => {
  console.log('value1', value);
  return new MyPromise((resolve, reject) => {
    resolve(456);
  }).then((value) => {
    return new MyPromise((resolve, reject) => {
      resolve(789);
    })
  });
}, (reason) => {
  console.log('reason1', reason);
}).then((value) => {
  console.log('value2', value);
}, (reason) => {
  console.log('reason2', reason);
});
复制代码

打印结果:

value1 123  value2 789

6. 让 then() 方法的回调函数总是异步调用

官方 Promise 实现的回调函数总是异步调用的:

console.log('start');

let promise = new Promise((resolve, reject) => {
  console.log('step-');
  resolve(123);
});

promise.then((value) => {
  console.log('step--');
  console.log('value', value);
});

console.log('end');
复制代码

打印结果:

start  step-  end  step--  value1 123

Promise属于微任务,这里我们为了方便用宏任务 setTiemout 来代替实现异步,具体关于宏任务、微任务以及Event Loop可以参考我的另一篇文章 带你彻底弄懂Event Loop

MyPromise.js

MyPromise.prototype.then = function(onFuifilled, onRejected) {
  let self = this;
  let promise2 = null;

  promise2 = new MyPromise((resolve, reject) => {
    if (self.state === PENDING) {
      self.onFulfilledCallbacks.push(() => {
        setTimeout(() => {
          try {
            let x = onFuifilled(self.value);
            self.resolvePromise(promise2, x, resolve, reject);
          } catch (reason) {
            reject(reason);
          }
        }, 0);
      });
      self.onRejectedCallbacks.push(() => {
        setTimeout(() => {
          try {
            let x = onRejected(self.reason);
            self.resolvePromise(promise2, x, resolve, reject);
          } catch (reason) {
            reject(reason);
          }
        }, 0);
      });
    }
  
    if (self.state === FULFILLED) {
      setTimeout(() => {
        try {
          let x = onFuifilled(self.value);
          self.resolvePromise(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason);
        }
      }, 0);
    }
  
    if (self.state === REJECTED) {
      setTimeout(() => {
        try {
          let x = onRejected(self.reason);
          self.resolvePromise(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason);
        }
      }, 0);
    }
  });

  return promise2;
};
复制代码

测试:

test.js

let MyPromise = require('./MyPromise.js');

console.log('start');

let promise = new MyPromise((resolve, reject) => {
  console.log('step-');
  setTimeout(() => {
    resolve(123);
  }, 1000);
});

promise.then((value) => {
  console.log('step--');
  console.log('value', value);
});

console.log('end');
复制代码

打印结果:

start  step-  end  step--  value1 123

经过以上步骤,一个最基本的Promise就已经实现完了,下面我们会实现一些不在PromiseA+规范的扩展方法。

7. 实现 catch() 方法

then() 方法的 onFulfilledonRejected 回调函数都不是必传项,如果不传,那么我们就无法接收 reject(reason) 中的错误,这时我们可以通过链式调用 catch() 方法用来接收错误。举例:

let promise = new Promise((resolve, reject) => {
  reject('has error');
});

promise.then((value) => {
  console.log('value', value);
}).catch((reason) => {
  console.log('reason', reason);
});
复制代码

打印结果:

reason has error

不仅如此, catch() 可以作为Promise链式调用的最后一步,前面Promise发生的错误会冒泡到最后一个 catch() 中,从而捕获异常。举例:

let promise = new Promise((resolve, reject) => {
  resolve(123);
});

promise.then((value) => {
  console.log('value', value);
  return new Promise((resolve, reject) => {
    reject('has error1');
  });
}).then((value) => {
  console.log('value', value);
  return new Promise((resolve, reject) => {
    reject('has error2');
  });
}).catch((reason) => {
  console.log('reason', reason);
});
复制代码

打印结果:

reason has error  reason has error1

那么 catch() 方法到底是如何实现的呢?

答案就是在Promise的实现中, onFulfilledonRejected 函数是有默认值的:

MyPromise.js

MyPromise.prototype.then = function(onFuifilled, onRejected) {
  onFuifilled = typeof onFuifilled === 'function' ? onFuifilled : value => {return value;};
  onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason};
};

MyPromise.prototype.catch = function(onRejected) {
  return this.then(null, onRejected);
};
复制代码

可以看到, onRejected 的默认值是把错误 reason 通过 throw 抛出去。由于我们对于同步代码的执行都是在 try...catch 中的,所以如果Promise发生了错误,如果没传 onRejected ,默认的函数会把错误 reason 抛出,然后会被promise2捕捉到,作为 reject(reason) 决议。

catch() 实现就是调用 this.then(null, onRejected) ,由于 promise2reject ,所以会执行 onRejected 回调,于是就捕捉到了第一个promise的错误。

总结来说, then() 方法中不传 onRejected 回调, Promise 内部会默认帮你写一个函数作为回调,作用就是 throw 抛出 reject 或者 try...catch 到的错误,然后错误 reason 会被 promise2 作为 reject(reason) 进行决议,于是会被下一个 then() 方法的 onRejected 回调函数调用,而 catch 只是写了一个特殊的 then(null, onRejected) 而已。

所以,我们在写 Promise 的链式调用的时候,在 then() 中可以不传 onRejected 回调,只需要在链式调用的最末尾加一个 catch() 就可以了,这样在该链条中的 Promise 发生的错误都会被最后的 catch 捕获到。

举例1:

let promise = new Promise((resolve, reject) => {
  reject(123);
});

promise.then((value) => {
  // 注意,不会走这里,因为第一个promise是被reject的
  console.log('value1', value);
  return new Promise((resolve, reject) => {
    reject('has error1');
  });
}).then((value) => {
  console.log('value2', value);
  return new Promise((resolve, reject) => {
    reject('has error2');
  });
}, (reason) => {
  // 注意,这个then有onRejected回调
  console.log('reason2', reason);
}).catch((reason) => {
  // 错误在上一个then就被捕获了,所以不会走到这里
  console.log('reason3', reason);
});
复制代码

打印结果:

reason2 123

举例2:

let promise = new Promise((resolve, reject) => {
  reject(123);
});

promise.then((value) => {
  console.log('value1', value);
  return new Promise((resolve, reject) => {
    reject('has error1');
  });
}).then((value) => {
  console.log('value2', value);
  return new Promise((resolve, reject) => {
    reject('has error2');
  });
}).catch((reason) => {
  // 由于链条中的then都没有onRejected回调,所以会一直被冒泡到最后的catch这里
  console.log('reason3', reason);
});
复制代码

catchthen 一样都是返回一个新的 Promise 。有的同学可能会有疑问,如果 catch 中的回调执行也发生错误该怎么办呢,这个我们后续在Promise异常处理中再做讨论。

打印结果:

reason3 123

8. 实现 finally 方法

finally 是某些库对 Promise 实现的一个扩展方法,无论是 resolve 还是 reject ,都会走 finally 方法。

MyPromise.js

MyPromise.prototype.finally = function(fn) {
    return this.then(value => {
       fn();
       return value;
    }, reason => {
        fn();
        throw reason;
    });
};
复制代码

9. 实现 done 方法

done 方法作为 Promise 链式调用的最后一步,用来向全局抛出没有被 Promise 内部捕获的错误,并且不再返回一个 Promise 。一般用来结束一个 Promise 链。

MyPromise.js

MyPromise.prototype.done = function() {
    this.catch(reason => {
        console.log('done', reason);
        throw reason;
    });
};
复制代码

10. 实现 Promise.all 方法

Promise.all() 接收一个包含多个 Promise 的数组,当所有 Promise 均为 fulfilled 状态时,返回一个结果数组,数组中结果的顺序和传入的 Promise 顺序一一对应。如果有一个 Promiserejected 状态,则整个 Promise.allrejected

MyPromise.js

MyPromise.all = function(promiseArr) {
  return new MyPromise((resolve, reject) => {
    let result = [];

    promiseArr.forEach((promise, index) => {
      promise.then((value) => {
        result[index] = value;

        if (result.length === promiseArr.length) {
          resolve(result);
        }
      }, reject);
    });
  });
};
复制代码

test.js

let MyPromise = require('./MyPromise.js');

let promise1 = new MyPromise((resolve, reject) => {
  console.log('aaaa');
  setTimeout(() => {
    resolve(1111);
    console.log(1111);
  }, 1000);
});

let promise2 = new MyPromise((resolve, reject) => {
  console.log('bbbb');
  setTimeout(() => {
    reject(2222);
    console.log(2222);
  }, 2000);
});

let promise3 = new MyPromise((resolve, reject) => {
  console.log('cccc');
  setTimeout(() => {
    resolve(3333);
    console.log(3333);
  }, 3000);
});

Promise.all([promise1, promise2, promise3]).then((value) => {
  console.log('all value', value);
}, (reason) => {
  console.log('all reason', reason);
})
复制代码

打印结果:

aaaa  bbbb  cccc  1111  2222  all reason 2222  3333

11. 实现 Promise.reace 方法

Promise.race() 接收一个包含多个 Promise 的数组,当有一个 Promisefulfilled 状态时,整个大的 Promiseonfulfilled ,并执行 onFulfilled 回调函数。如果有一个 Promiserejected 状态,则整个 Promise.racerejected

MyPromise.js

MyPromise.race = function(promiseArr) {
  return new MyPromise((resolve, reject) => {
    promiseArr.forEach(promise => {
      promise.then((value) => {
        resolve(value);   
      }, reject);
    });
  });
};
复制代码

test.js

let MyPromise = require('./MyPromise.js');

let promise1 = new MyPromise((resolve, reject) => {
  console.log('aaaa');
  setTimeout(() => {
    resolve(1111);
    console.log(1111);
  }, 1000);
});

let promise2 = new MyPromise((resolve, reject) => {
  console.log('bbbb');
  setTimeout(() => {
    reject(2222);
    console.log(2222);
  }, 2000);
});

let promise3 = new MyPromise((resolve, reject) => {
  console.log('cccc');
  setTimeout(() => {
    resolve(3333);
    console.log(3333);
  }, 3000);
});

Promise.all([promise1, promise2, promise3]).then((value) => {
  console.log('all value', value);
}, (reason) => {
  console.log('all reason', reason);
})
复制代码

打印结果:

aaaa  bbbb  cccc  1111  all reason 1111  2222  3333

12. 实现 Promise.resolve 方法

Promise.resolve 用来生成一个 fulfilled 完成态的 Promise ,一般放在整个 Promise 链的开头,用来开始一个 Promise 链。

MyPromise.js

MyPromise.resolve = function(value) {
  let promise;

  promise = new MyPromise((resolve, reject) => {
    this.prototype.resolvePromise(promise, value, resolve, reject);
  });

  return promise;
};
复制代码

test.js

let MyPromise = require('./MyPromise.js');

MyPromise.resolve(1111).then((value) => {
  console.log('value1', value);
  return new MyPromise((resolve, reject) => {
    resolve(2222);
  })
}).then((value) => {
  console.log('value2', value);
})
复制代码

打印结果:

value1 1111  value2 2222

由于传入的 value 有可能是普通值,有可能是 thenable ,也有可能是另一个 Promise ,所以调用 resolvePromise 进行解析。

12. 实现 Promise.reject 方法

Promise.reject 用来生成一个 rejected 失败态的 Promise

MyPromise.js

MyPromise.reject = function(reason) {
  return new MyPromise((resolve, reject) => {
    reject(reason);
  });
};
复制代码

test.js

let MyPromise = require('./MyPromise.js');

MyPromise.reject(1111).then((value) => {
  console.log('value1', value);
  return new MyPromise((resolve, reject) => {
    resolve(2222);
  })
}).then((value) => {
  console.log('value2', value);
}).catch(reason => {
  console.log('reason', reason);
});
复制代码

打印结果:

reason 1111

13. 实现 Promise.deferred 方法

Promise.deferred 可以用来延迟执行 resolvereject

MyPromise.js

MyPromise.deferred = function() {
    let dfd = {};
    dfd.promies = new MyPromise((resolve, reject) => {
      dfd.resolve = resolve;
      dfd.rfeject = reject;
    });
    return dfd;
};
复制代码

这样,你就可以在外部通过调用 dfd.resolve()dfd.reject() 来决议该 Promise

13. 如何停止一个 Promise

假设这样一个场景,我们有一个很长的 Promise 链式调用,这些 Promise 是依次依赖的关系,如果链条中的某个 Promise 出错了,就不需要再向下执行了,默认情况下,我们是无法实现这个需求的,因为 Promise 无论是 then 还是 catch 都会返回一个 Promise ,都会继续向下执行 thencatch 。举例:

new Promise(function(resolve, reject) {
  resolve(1111)
}).then(function(value) {
  // "ERROR!!!"
}).catch()
  .then()
  .then()
  .catch()
  .then()
复制代码

有没有办法让这个链式调用在ERROR!!!的后面就停掉,完全不去执行链式调用后面所有回调函数呢?

我们自己封装一个 Promise.stop 方法。

MyPromise.js

MyPromise.stop = function() {
  return new Promise(function() {});
};
复制代码

stop 中返回一个永远不执行 resolve 或者 rejectPromise ,那么这个 Promise 永远处于 pending 状态,所以永远也不会向下执行 thencatch 了。这样我们就停止了一个 Promise 链。

new MyPromise(function(resolve, reject) {
  resolve(1111)
}).then(function(value) {
  // "ERROR!!!"
  MyPromise.stop();
}).catch()
  .then()
  .then()
  .catch()
  .then()
复制代码

但是这样会有一个缺点,就是链式调用后面的所有回调函数都无法被垃圾回收器回收。

14. 如何解决 Promise 链上返回的最后一个 Promise 出现错误

看如下例子:

new Promise(function(resolve) {
  resolve(42)
}).then(function(value) {
  a.b = 2;
});
复制代码

这里a不存在,所以给a.b赋值是一个语法错误, onFulfilled 回调函数是包在 try...catch 中执行的,错误会被 catch 到,但是由于后面没有 thencatch 了,这个错误无法被处理,就会被 Promise 吃掉,没有任何异常,这就是常说的 Promise有可能会吃掉错误

那么我们怎么处理这种情况呢?

方法一

就是我们前面已经实现过的 done()

new Promise(function(resolve) {
  resolve(42)
}).then(function(value) {
  a.b = 2;
}).done();
复制代码

done() 方法相当于一个 catch ,但是却不再返回 Promise 了,注意 done() 方法中不能出现语法错误,否则又无法捕获了。

方法二

普通错误监听 windowerror 事件可以实现捕获

window.addEventListener('error', error => {
  console.log(error); // 不会触发
});
复制代码

Promise没有被 onRejected() 处理的错误需要监听 unhandledrejection 事件

window.addEventListener('unhandledrejection', error => {
  console.log('unhandledrejection', error); // 可以触发,而且还可以直接拿到 promise 对象
});
复制代码

14. 单元测试

结束

相关单元测试以及完整代码可以到我的 github 查看,如果对你有帮助的话,就来个star吧~

从0到1实现Promise
从0到1实现Promise

以上所述就是小编给大家介绍的《从0到1实现Promise》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

订阅

订阅

[美] 罗伯特·金奇尔、马尼·佩伊万 / 中信出版集团 / 2018-12 / 68.00元

数据显示,年轻人现在每天看视频的时间已经超过电视。YouTube 平台每天的视频观看总时长超过10亿小时,这个数字还在增长。数字视频牢牢占据着人们的注意力。 数字时代如何实现创意变现?视频平台如何提升自己的品牌认知和广告号召力?想要在这个庞大的媒体生态中占据流量入口,你需要先了解 YouTube。在过去的10年里,互联网视频平台 YouTube 已经像60多年前的电影、广播和电视的发明一样,......一起来看看 《订阅》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具