Promise是前端面试中的高频问题,我作为面试官的时候,问Promise的概率超过90%,据我所知,大多数公司,都会问一些关于Promise的问题。如果你能根据PromiseA+的规范,写出符合规范的源码,那么我想,对于面试中的Promise相关的问题,都能够给出比较完美的答案。

我的建议是,对照规范多写几次实现,也许第一遍的时候,是改了多次,才能通过测试,那么需要反复的写,我已经将Promise的源码实现写了不下七遍。

Promise的源码实现

  1. /**
  2. * 1. new Promise时,需要传递一个 executor 执行器,执行器立刻执行
  3. * 2. executor 接受两个参数,分别是 resolve 和 reject
  4. * 3. promise 只能从 pending 到 rejected, 或者从 pending 到 fulfilled
  5. * 4. promise 的状态一旦确认,就不会再改变
  6. * 5. promise 都有 then 方法,then 接收两个参数,分别是 promise 成功的回调 onFulfilled,
  7. * 和 promise 失败的回调 onRejected
  8. * 6. 如果调用 then 时,promise已经成功,则执行 onFulfilled,并将promise的值作为参数传递进去。
  9. * 如果promise已经失败,那么执行 onRejected, 并将 promise 失败的原因作为参数传递进去。
  10. * 如果promise的状态是pending,需要将onFulfilled和onRejected函数存放起来,等待状态确定后,再依次将对应的函数执行(发布订阅)
  11. * 7. then 的参数 onFulfilled 和 onRejected 可以缺省
  12. * 8. promise 可以then多次,promise 的then 方法返回一个 promise
  13. * 9. 如果 then 返回的是一个结果,那么就会把这个结果作为参数,传递给下一个then的成功的回调(onFulfilled)
  14. * 10. 如果 then 中抛出了异常,那么就会把这个异常作为参数,传递给下一个then的失败的回调(onRejected)
  15. * 11.如果 then 返回的是一个promise,那么需要等这个promise,那么会等这个promise执行完,promise如果成功,
  16. * 就走下一个then的成功,如果失败,就走下一个then的失败
  17. */
  18.  
  19. const PENDING = 'pending';
  20. const FULFILLED = 'fulfilled';
  21. const REJECTED = 'rejected';
  22. function Promise(executor) {
  23. let self = this;
  24. self.status = PENDING;
  25. self.onFulfilled = [];//成功的回调
  26. self.onRejected = []; //失败的回调
  27. //PromiseA+ 2.1
  28. function resolve(value) {
  29. if (self.status === PENDING) {
  30. self.status = FULFILLED;
  31. self.value = value;
  32. self.onFulfilled.forEach(fn => fn());//PromiseA+ 2.2.6.1
  33. }
  34. }
  35.  
  36. function reject(reason) {
  37. if (self.status === PENDING) {
  38. self.status = REJECTED;
  39. self.reason = reason;
  40. self.onRejected.forEach(fn => fn());//PromiseA+ 2.2.6.2
  41. }
  42. }
  43.  
  44. try {
  45. executor(resolve, reject);
  46. } catch (e) {
  47. reject(e);
  48. }
  49. }
  50.  
  51. Promise.prototype.then = function (onFulfilled, onRejected) {
  52. //PromiseA+ 2.2.1 / PromiseA+ 2.2.5 / PromiseA+ 2.2.7.3 / PromiseA+ 2.2.7.4
  53. onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
  54. onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
  55. let self = this;
  56. //PromiseA+ 2.2.7
  57. let promise2 = new Promise((resolve, reject) => {
  58. if (self.status === FULFILLED) {
  59. //PromiseA+ 2.2.2
  60. //PromiseA+ 2.2.4 --- setTimeout
  61. setTimeout(() => {
  62. try {
  63. //PromiseA+ 2.2.7.1
  64. let x = onFulfilled(self.value);
  65. resolvePromise(promise2, x, resolve, reject);
  66. } catch (e) {
  67. //PromiseA+ 2.2.7.2
  68. reject(e);
  69. }
  70. });
  71. } else if (self.status === REJECTED) {
  72. //PromiseA+ 2.2.3
  73. setTimeout(() => {
  74. try {
  75. let x = onRejected(self.reason);
  76. resolvePromise(promise2, x, resolve, reject);
  77. } catch (e) {
  78. reject(e);
  79. }
  80. });
  81. } else if (self.status === PENDING) {
  82. self.onFulfilled.push(() => {
  83. setTimeout(() => {
  84. try {
  85. let x = onFulfilled(self.value);
  86. resolvePromise(promise2, x, resolve, reject);
  87. } catch (e) {
  88. reject(e);
  89. }
  90. });
  91. });
  92. self.onRejected.push(() => {
  93. setTimeout(() => {
  94. try {
  95. let x = onRejected(self.reason);
  96. resolvePromise(promise2, x, resolve, reject);
  97. } catch (e) {
  98. reject(e);
  99. }
  100. });
  101. });
  102. }
  103. });
  104. return promise2;
  105. }
  106.  
  107. function resolvePromise(promise2, x, resolve, reject) {
  108. let self = this;
  109. //PromiseA+ 2.3.1
  110. if (promise2 === x) {
  111. reject(new TypeError('Chaining cycle'));
  112. }
  113. if (x && typeof x === 'object' || typeof x === 'function') {
  114. let used; //PromiseA+2.3.3.3.3 只能调用一次
  115. try {
  116. let then = x.then;
  117. if (typeof then === 'function') {
  118. //PromiseA+2.3.3
  119. then.call(x, (y) => {
  120. //PromiseA+2.3.3.1
  121. if (used) return;
  122. used = true;
  123. resolvePromise(promise2, y, resolve, reject);
  124. }, (r) => {
  125. //PromiseA+2.3.3.2
  126. if (used) return;
  127. used = true;
  128. reject(r);
  129. });
  130.  
  131. }else{
  132. //PromiseA+2.3.3.4
  133. if (used) return;
  134. used = true;
  135. resolve(x);
  136. }
  137. } catch (e) {
  138. //PromiseA+ 2.3.3.2
  139. if (used) return;
  140. used = true;
  141. reject(e);
  142. }
  143. } else {
  144. //PromiseA+ 2.3.3.4
  145. resolve(x);
  146. }
  147. }
  148.  
  149. module.exports = Promise;

有专门的测试脚本可以测试所编写的代码是否符合PromiseA+的规范。

首先,在promise实现的代码中,增加以下代码:

  1. Promise.defer = Promise.deferred = function () {
  2. let dfd = {};
  3. dfd.promise = new Promise((resolve, reject) => {
  4. dfd.resolve = resolve;
  5. dfd.reject = reject;
  6. });
  7. return dfd;
  8. }

安装测试脚本:

  1. npm install -g promises-aplus-tests

如果当前的promise源码的文件名为promise.js

那么在对应的目录执行以下命令:

  1. promises-aplus-tests promise.js

promises-aplus-tests中共有872条测试用例。以上代码,可以完美通过所有用例。

对上面的代码实现做一点简要说明(其它一些内容注释中已经写得很清楚):

  1. onFulfilled 和 onFulfilled的调用需要放在setTimeout,因为规范中表示: onFulfilled or onRejected must not be called until the execution context stack contains only platform code。使用setTimeout只是模拟异步,原生Promise并非是这样实现的。

  2. 在 resolvePromise 的函数中,为何需要usedd这个flag,同样是因为规范中明确表示: If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored. 因此我们需要这样的flag来确保只会执行一次。

  3. self.onFulfilled 和 self.onRejected 中存储了成功的回调和失败的回调,根据规范2.6显示,当promise从pending态改变的时候,需要按照顺序去指定then对应的回调。

PromiseA+的规范(翻译版)

PS: 下面是我翻译的规范,供参考

术语

  1. promise 是一个有then方法的对象或者是函数,行为遵循本规范
  2. thenable 是一个有then方法的对象或者是函数
  3. value 是promise状态成功时的值,包括 undefined/thenable或者是 promise
  4. exception 是一个使用throw抛出的异常值
  5. reason 是promise状态失败时的值

要求

2.1 Promise States

Promise 必须处于以下三个状态之一: pending, fulfilled 或者是 rejected

2.1.1 如果promise在pending状态
  1. 2.1.1.1 可以变成 fulfilled 或者是 rejected
2.1.2 如果promise在fulfilled状态
  1. 2.1.2.1 不会变成其它状态
  2. 2.1.2.2 必须有一个value
2.1.3 如果promise在rejected状态
  1. 2.1.3.1 不会变成其它状态
  2. 2.1.3.2 必须有一个promiserejectreason

概括即是:promise的状态只能从pending变成fulfilled,或者从pending变成rejected.promise成功,有成功的value.promise失败的话,有失败的原因

2.2 then方法

promise必须提供一个then方法,来访问最终的结果

promise的then方法接收两个参数

  1. promise.then(onFulfilled, onRejected)
2.2.1 onFulfilled 和 onRejected 都是可选参数
  1. 2.2.1.1 onFulfilled 必须是函数类型
  2. 2.2.1.2 onRejected 必须是函数类型
2.2.2 如果 onFulfilled 是函数:
  1. 2.2.2.1 必须在promise变成 fulfilled 时,调用 onFulfilled,参数是promisevalue
  2. 2.2.2.2 promise的状态不是 fulfilled 之前,不能调用
  3. 2.2.2.3 onFulfilled 只能被调用一次
2.2.3 如果 onRejected 是函数:
  1. 2.2.3.1 必须在promise变成 rejected 时,调用 onRejected,参数是promisereason
  2. 2.2.3.2 promise的状态不是 rejected 之前,不能调用
  3. 2.2.3.3 onRejected 只能被调用一次
2.2.4 onFulfilled 和 onRejected 应该是微任务
2.2.5 onFulfilled 和 onRejected 必须作为函数被调用
2.2.6 then方法可能被多次调用
  1. 2.2.6.1 如果promise变成了 fulfilled态,所有的onFulfilled回调都需要按照then的顺序执行
  2. 2.2.6.2 如果promise变成了 rejected态,所有的onRejected回调都需要按照then的顺序执行
2.2.7 then必须返回一个promise
  1. promise2 = promise1.then(onFulfilled, onRejected);
  1. 2.2.7.1 onFulfilled onRejected 执行的结果为x,调用 resolvePromise
  2. 2.2.7.2 如果 onFulfilled 或者 onRejected 执行时抛出异常e,promise2需要被reject
  3. 2.2.7.3 如果 onFulfilled 不是一个函数,promise2 promise1的值fulfilled
  4. 2.2.7.4 如果 onRejected 不是一个函数,promise2 promise1reason rejected

2.3 resolvePromise

resolvePromise(promise2, x, resolve, reject)

2.3.1 如果 promise2 和 x 相等,那么 reject promise with a TypeError
2.3.2 如果 x 是一个 promsie
  1. 2.3.2.1 如果xpending态,那么promise必须要在pending,直到 x 变成 fulfilled or rejected.
  2. 2.3.2.2 如果 x fulfilled, fulfill promise with the same value.
  3. 2.3.2.3 如果 x rejected, reject promise with the same reason.
2.3.3 如果 x 是一个 object 或者 是一个 function
  1. 2.3.3.1 let then = x.then.
  2. 2.3.3.2 如果 x.then 这步出错,那么 reject promise with e as the reason..
  3. 2.3.3.3 如果 then 是一个函数,then.call(x, resolvePromiseFn, rejectPromise)
  4. 2.3.3.3.1 resolvePromiseFn 入参是 y, 执行 resolvePromise(promise2, y, resolve, reject);
  5. 2.3.3.3.2 rejectPromise 入参是 r, reject promise with r.
  6. 2.3.3.3.3 如果 resolvePromise rejectPromise 都调用了,那么第一个调用优先,后面的调用忽略。
  7. 2.3.3.3.4 如果调用then抛出异常e
  8. 2.3.3.3.4.1 如果 resolvePromise rejectPromise 已经被调用,那么忽略
  9. 2.3.3.3.4.3 否则,reject promise with e as the reason
  10. 2.3.3.4 如果 then 不是一个function. fulfill promise with x.
2.3.4 如果 x 不是一个 object 或者 function,fulfill promise with x.

Promise的其他方法

虽然上述的promise源码已经符合PromiseA+的规范,但是原生的Promise还提供了一些其他方法,如:

  1. Promise.resolve()
  2. Promise.reject()
  3. Promise.prototype.catch()
  4. Promise.prototype.finally()
  5. Promise.all()
  6. Promise.race()

下面具体说一下每个方法的实现:

Promise.resolve

Promise.resolve(value) 返回一个以给定值解析后的Promise 对象.

  1. 如果 value 是个 thenable 对象,返回的promise会“跟随”这个thenable的对象,采用它的最终状态
  2. 如果传入的value本身就是promise对象,那么Promise.resolve将不做任何修改、原封不动地返回这个promise对象。
  3. 其他情况,直接返回以该值为成功状态的promise对象。
  1. Promise.resolve = function (param) {
  2. if (param instanceof Promise) {
  3. return param;
  4. }
  5. return new Promise((resolve, reject) => {
  6. if (param && param.then && typeof param.then === 'function') {
  7. setTimeout(() => {
  8. param.then(resolve, reject);
  9. });
  10. } else {
  11. resolve(param);
  12. }
  13. });
  14. }

thenable对象的执行加 setTimeout的原因是根据原生Promise对象执行的结果推断的,如下的测试代码,原生的执行结果为: 20 400 30;为了同样的执行顺序,增加了setTimeout延时。

测试代码:

  1. let p = Promise.resolve(20);
  2. p.then((data) => {
  3. console.log(data);
  4. });
  5.  
  6. let p2 = Promise.resolve({
  7. then: function(resolve, reject) {
  8. resolve(30);
  9. }
  10. });
  11.  
  12. p2.then((data)=> {
  13. console.log(data)
  14. });
  15.  
  16. let p3 = Promise.resolve(new Promise((resolve, reject) => {
  17. resolve(400)
  18. }));
  19. p3.then((data) => {
  20. console.log(data)
  21. });

Promise.reject

Promise.reject方法和Promise.resolve不同,Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。

  1. Promise.reject = function (reason) {
  2. return new Promise((resolve, reject) => {
  3. reject(reason);
  4. });
  5. }

Promise.prototype.catch

Promise.prototype.catch 用于指定出错时的回调,是特殊的then方法,catch之后,可以继续 .then

  1. Promise.prototype.catch = function (onRejected) {
  2. return this.then(null, onRejected);
  3. }

Promise.prototype.finally

不管成功还是失败,都会走到finally中,并且finally之后,还可以继续then。并且会将值原封不动的传递给后面的then.

  1. Promise.prototype.finally = function (callback) {
  2. return this.then((value) => {
  3. return Promise.resolve(callback()).then(() => {
  4. return value;
  5. });
  6. }, (err) => {
  7. return Promise.resolve(callback()).then(() => {
  8. throw err;
  9. });
  10. });
  11. }

Promise.all

Promise.all(promises) 返回一个promise对象

  1. 如果传入的参数是一个空的可迭代对象,那么此promise对象回调完成(resolve),只有此情况,是同步执行的,其它都是异步返回的。
  2. 如果传入的参数不包含任何 promise,则返回一个异步完成.
  3. promises 中所有的promise都promise都“完成”时或参数中不包含 promise 时回调完成。
  4. 如果参数中有一个promise失败,那么Promise.all返回的promise对象失败
  5. 在任何情况下,Promise.all 返回的 promise 的完成状态的结果都是一个数组
  1. Promise.all = function (promises) {
  2. return new Promise((resolve, reject) => {
  3. let index = 0;
  4. let result = [];
  5. if (promises.length === 0) {
  6. resolve(result);
  7. } else {
  8. function processValue(i, data) {
  9. result[i] = data;
  10. if (++index === promises.length) {
  11. resolve(result);
  12. }
  13. }
  14. for (let i = 0; i < promises.length; i++) {
  15. //promises[i] 可能是普通值
  16. Promise.resolve(promises[i]).then((data) => {
  17. processValue(i, data);
  18. }, (err) => {
  19. reject(err);
  20. return;
  21. });
  22. }
  23. }
  24. });
  25. }

测试代码:

  1. var promise1 = new Promise((resolve, reject) => {
  2. resolve(3);
  3. })
  4. var promise2 = 42;
  5. var promise3 = new Promise(function(resolve, reject) {
  6. setTimeout(resolve, 100, 'foo');
  7. });
  8.  
  9. Promise.all([promise1, promise2, promise3]).then(function(values) {
  10. console.log(values); //[3, 42, 'foo']
  11. },(err)=>{
  12. console.log(err)
  13. });
  14.  
  15. var p = Promise.all([]); // will be immediately resolved
  16. var p2 = Promise.all([1337, "hi"]); // non-promise values will be ignored, but the evaluation will be done asynchronously
  17. console.log(p);
  18. console.log(p2)
  19. setTimeout(function(){
  20. console.log('the stack is now empty');
  21. console.log(p2);
  22. });

Promise.race

Promise.race函数返回一个 Promise,它将与第一个传递的 promise 相同的完成方式被完成。它可以是完成( resolves),也可以是失败(rejects),这要取决于第一个完成的方式是两个中的哪个。

如果传的参数数组是空,则返回的 promise 将永远等待。

如果迭代包含一个或多个非承诺值和/或已解决/拒绝的承诺,则 Promise.race 将解析为迭代中找到的第一个值。

  1. Promise.race = function (promises) {
  2. return new Promise((resolve, reject) => {
  3. if (promises.length === 0) {
  4. return;
  5. } else {
  6. for (let i = 0; i < promises.length; i++) {
  7. Promise.resolve(promises[i]).then((data) => {
  8. resolve(data);
  9. return;
  10. }, (err) => {
  11. reject(err);
  12. return;
  13. });
  14. }
  15. }
  16. });
  17. }

测试代码:

  1. Promise.race([
  2. new Promise((resolve, reject) => { setTimeout(() => { resolve(100) }, 1000) }),
  3. undefined,
  4. new Promise((resolve, reject) => { setTimeout(() => { reject(100) }, 100) })
  5. ]).then((data) => {
  6. console.log('success ', data);
  7. }, (err) => {
  8. console.log('err ',err);
  9. });
  10.  
  11. Promise.race([
  12. new Promise((resolve, reject) => { setTimeout(() => { resolve(100) }, 1000) }),
  13. new Promise((resolve, reject) => { setTimeout(() => { resolve(200) }, 200) }),
  14. new Promise((resolve, reject) => { setTimeout(() => { reject(100) }, 100) })
  15. ]).then((data) => {
  16. console.log(data);
  17. }, (err) => {
  18. console.log(err);
  19. });

Promise的源码实现(完美符合Promise/A+规范)的更多相关文章

  1. Promise的源码实现(符合Promise/A+规范)

    我们手写一个Promise/A+规范,然后安装测试脚本,以求通过这个规范. //Promise/A+源代码 // new Promise时,需要传递一个executor执行器,执行器立即执行 // e ...

  2. JS魔法堂: Native Promise Only源码剖析

    一, 前言 深入学习Promise的朋友应该都看过<深入理解Promise五部曲>这一系列的文章, 以解除回调地狱之外的观点来剖析Promise更多的内涵,确实十分精彩. Part 1: ...

  3. promise/bluebird源码

    本作品采用知识共享署名 4.0 国际许可协议进行许可.转载保留声明头部与原文链接https://luzeshu.com/blog/bluebirdsource 本博客同步在http://www.cnb ...

  4. 异步解决方案promise及源码实现

    js语言的特性,造就了特别的异步处理方式,我记得以前用的最多的就是回调函数,那个时候写jquery的ajax时候,特别喜欢写这种代码: $.ajax({ method:'get', url:" ...

  5. promise源码解析

    前言 大部分同学对promise,可能还停留在会使用es6的promise,还没有深入学习.我们都知道promise内部通过reslove.reject来判断执行哪个函数,原型上面的then同样的,也 ...

  6. 从源码看 Promise 概念与实现

    Promise 是 JS 异步编程中的重要概念,它较好地解决了异步任务中回调嵌套的问题.在没有引入新的语言机制的前提下,这是如何实现的呢?上手 Promise 时常见若干晦涩的 API 与概念,它们又 ...

  7. 这一次,彻底理解Promise源码思想

    关于Promise的源码实现,网上有太多答案,我也看过很多资料,但都不是很明白.直到有一天我学完函数式编程之函子的概念,才对Promise源码有了更深刻的认识.今天,就让我们来重新认识一下Promis ...

  8. 从源码上理解Netty并发工具-Promise

    前提 最近一直在看Netty相关的内容,也在编写一个轻量级的RPC框架来练手,途中发现了Netty的源码有很多亮点,某些实现甚至可以用苛刻来形容.另外,Netty提供的工具类也是相当优秀,可以开箱即用 ...

  9. Netty 源码解析(三): Netty 的 Future 和 Promise

    今天是猿灯塔“365篇原创计划”第三篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源码解析(二): Netty 的 Channel 当前:Ne ...

随机推荐

  1. ecstore中怎样使用ajax提交数据

    //javascript代码 $$(".bb").addEvent('change',function(e){ var order_item_id = this.get('orde ...

  2. HTML文档命名规则

    HTML文档是展示Web前段开发工程师成果的最好表示方式,为了便于文档规范化管理,在编写HTML文档时,必须遵循HTML文件命名规则. HTML文档命名规则如下: (1)文档的扩展名为htm或者htm ...

  3. 移动开发基础-学习笔记二-字体图标、less、bootstrap入门

    1.字体图标 1.字体图标都是用svg图片 1.svg图片不失真 2.svg图标由设计师提供 3.为了减少网络请求,会把svg图标转换成字体图标,放到字体文件中,通过字体库的方式使用 2.制作步骤 1 ...

  4. (办公)mybatis工作中常见的问题(不定时更新)

    1.mybatis的like查询的方式. <if test="shopName != null and shopName != ''"> <bind name=& ...

  5. 谈谈git以及如何关联github

    git :一款免费.开源的分布式代码版本管理控制系统 记录当前产品代码的所有版本信息,包括历史修改信息 方便快速回退到某一个具体的版本 方便团队协作开发 可检测代码冲突.合并代码等 1.利用 git ...

  6. 【SpringBoot笔记】SpringBoot如何正确关闭应用

    关闭Spring Boot应用程序,我们可以通过OS命令kill -9 进程ID 实现将进程杀死.但是,有没有一种更好的方式,比如通过REST请求实现?Spring Boot Actoator提供了实 ...

  7. 一:SqlServer中的 CEILING函数和 FLOOR函数以及ROUND()

    例如 1.ROUND() 格式为ROUND(y1,y2,y3) y1:要被四舍五入的数字y2:保留的小数位数 y3:为0,可以不写,y1进行四舍五入,不为0则y1不进入四舍五入,如果y1有值就直接根据 ...

  8. Linux(Manjaro) - Docker - MySQL 安装配置

    Linux(Manjaro) - Docker - MySQL 安装配置 拉取mysql镜像 # 使用网易的 MySQL 镜像地址 docker pull hub.c.163.com/library/ ...

  9. [原创]一种专门用于前后端分离的web服务器(JerryServer)

    如果你还不了解现在的前后端分离,推荐阅读淘宝前端团队的前后端分离的思考与实践 1.问题 随着现在整个软件开发行业的发展,在开发模式上逐渐由以前的一个人完成服务端和前端web页面,演变为前端和后端逐渐分 ...

  10. Spring缓存注解@Cacheable、@CacheEvict、@CachePut使用(转)

    原文地址:https://www.cnblogs.com/fashflying/p/6908028.html 从3.1开始,Spring引入了对Cache的支持.其使用方法和原理都类似于Spring对 ...