Promise作为异步编程的一种解决方案,比传统的回调和事件更加强大,也是学习前端所必须要掌握的。作为一个有追求的前端,不仅要熟练掌握Promise的用法,而且要对其实现原理有一定的理解(说白了,就是面试装逼必备)。虽然网上有很多Promise的实现代码,几百行的,但个人觉得,不对异步编程和Promise有一定的理解,那些代码也就是一个板子而已(面试可不能敲板子)。首先默认读者都对Promise对象比较熟悉了,然后将从前端最常用的设计模式:发布-订阅和观察者模式的角度来一步一步的实现Promise。

  1. promise对象是commonJS工作组提出的一种规范,一种模式,目的是为了异步编程提供统一接口。
  2. promise是一种模式,promise可以帮忙管理异步方式返回的代码。他讲代码进行封装并添加一个类似于事件处理的管理层。我们可以使用promise来注册代码,这些代码会在在promise成功或者失败后运行。
  3. promise完成之后,对应的代码也会执行。我们可以注册任意数量的函数再成功或者失败后运行,也可以在任何时候注册事件处理程序。
  4. promise有两种状态:1、等待(pending);2、完成(settled)。
  5. promise会一直处于等待状态,直到它所包装的异步调用返回/超时/结束。
  6. 这时候promise状态变成完成。完成状态分成两类:1、解决(resolved);2、拒绝(rejected)。
  7. promise解决(resolved):意味着顺利结束。promise拒绝(rejected)意味着没有顺利结束。

    从异步编程说起

    既然Promise是一种异步解决方案,那么在没有Promise对象之前是怎么做异步处理的呢?有两种方法:回调函数和发布-订阅或观察者设计模式。(关于实现异步编程的更多方式请参考我的文章:JavaScript实现异步编程的5种方式

    回调函数(callback function)

    相信回调函数读者都不陌生,毕竟最早接触的也就是回调函数了,而且用回调函数做异步处理也很简单,以nodejs文件系统模块fs为例,读取一个文件一般都会这么做
  1. fs.readFile("h.js", (err, data) => {
  2. console.log(data.toString())
  3. });

其缺点也很明显,当异步流程变得复杂,那么回调也会变得很复杂,有时也叫做”回调地狱”,就以文件复制为例

  1. fs.exists("h.js", exists => { // 文件是否存在
  2. if (exists) {
  3. fs.readFile("h.js", (err, data) => { // 读文件
  4. fs.mkdir(__dirname + "/js/", err => { // 创建目录
  5. fs.writeFile(__dirname + "/js/h.js", data, err => { // 写文件
  6. console.log("复制成功,再回调下去,代码真的很难看得懂")
  7. })
  8. });
  9. });
  10. }
  11. });

其实代码还是能阅读的,感谢JS设计者没有把函数的花括号给去掉。像没有花括号的python写回调就是(就是个笑话。不是说python不好,毕竟JavaScript是世界上最好的语言)

  1. # 这代码属实没法看啊
  2. def callback_1():
  3. # processing ...
  4. def callback_2():
  5. # processing.....
  6. def callback_3():
  7. # processing ....
  8. def callback_4():
  9. #processing .....
  10. def callback_5():
  11. # processing ......
  12. async_function1(callback_5)
  13. async_function2(callback_4)
  14. async_function3(callback_3)
  15. async_function4(callback_2)
  16. async_function5(callback_1)复制代码

发布-订阅与观察者设计模式

第一次学设计模式还是在学Java和C++的时候,毕竟设计模式就是基于面向对象,让对象解耦而提出的。发布订阅设计模式和观察者模式很像,但是有点细微的区别(面试考点来了)

观察者模式 在软件设计中是一个对象,维护一个依赖列表,当任何状态发生改变自动通知它们。 发布-订阅模式是一种消息传递模式,消息的发布者**(Publishers)一般将消息发布到特定消息中心,订阅者(**Subscriber)可以按照自己的需求从消息中心订阅信息,跟消息队列挺类似的。

在观察者模式只有两种组件:接收者和发布者,而发布-订阅模式中则有三种组件:发布者、消息中心和接收者。
promise 模式 - 图1
在代码实现上的差异也比较明显

观察者设计模式

  1. // 观察者设计模式
  2. class Observer {
  3. constructor () {
  4. this.observerList = [];
  5. }
  6. subscribe (observer) {
  7. this.observerList.push(observer)
  8. }
  9. notifyAll (value) {
  10. this.observerList.forEach(observe => observe(value))
  11. }
  12. }复制代码

发布-订阅设计模式(nodejs EventEmitter)

  1. // 发布订阅
  2. class EventEmitter {
  3. constructor () {
  4. this.eventChannel = {}; // 消息中心
  5. }
  6. // subscribe
  7. on (event, callback) {
  8. this.eventChannel[event] ? this.eventChannel[event].push(callback) : this.eventChannel[event] = [callback]
  9. }
  10. // publish
  11. emit (event, ...args) {
  12. this.eventChannel[event] && this.eventChannel[event].forEach(callback => callback(...args))
  13. }
  14. // remove event
  15. remove (event) {
  16. if (this.eventChannel[event]) {
  17. delete this.eventChannel[event]
  18. }
  19. }
  20. // once event
  21. once (event, callback) {
  22. this.on(event, (...args) => {
  23. callback(...args);
  24. this.remove(event)
  25. })
  26. }
  27. }复制代码

从代码中也能看出他们的区别,观察者模式不对事件进行分类,当有事件时,将通知所有观察者。发布-订阅设计模式对事件进行了分类,触发不同的事件,将通知不同的观察者。所以可以认为后者就是前者的一个升级版,对通知事件做了更细粒度的划分。
发布-订阅和观察者在异步中的应用

  1. // 观察者
  2. const observer = new Observer();
  3. observer.subscribe(value => {
  4. console.log("第一个观察者,接收到的值为:");
  5. console.log(value)
  6. });
  7. observer.subscribe(value => {
  8. console.log("第二个观察者,接收到的值为");
  9. console.log(value)
  10. });
  11. fs.readFile("h.js", (err, data) => {
  12. observer.notifyAll(data.toString())
  13. });复制代码
  1. // 发布-订阅
  2. const event = new EventEmitter();
  3. event.on("err", console.log);
  4. event.on("data", data => {
  5. // do something
  6. console.log(data)
  7. });
  8. fs.readFile("h.js", (err, data) => {
  9. if (err) event.emit("err", err);
  10. event.emit("data", data.toString())
  11. });

两种设计模式在异步编程中,都是通过注册全局观察者或全局事件,然后在异步环境里通知所有观察者或触发特定事件来实现异步编程。
劣势也很明显,比如全局观察者/事件过多难以维护,事件名命冲突等等,因此Promise便诞生了。

从观察者设计模式的角度分析和实现Promise

Promise在一定程度上继承了观察者和发布-订阅设计模式的思想,我们先从一段Promise代码开始,来分析Promise是如何使用观察者设计模式

  1. const asyncReadFile = filename => new Promise((resolve) => {
  2. fs.readFile(filename, (err, data) => {
  3. resolve(data.toString()); // 发布者 相当于观察者模式的notifyAll(value) 或者发布订阅模式的emit
  4. });
  5. });
  6. asyncReadFile("h.js").then(value => { // 订阅者 相当于观察者模式的subscribe(value => console.log(value)) 或者发布订阅模式的on
  7. console.log(value);
  8. });

从上面的Promise代码中,我觉得Promise方案优于前面的发布-订阅/观察者方案的原因就是:对异步任务的封装,事件发布者在回调函数里(resolve),事件接收者在对象方法里(then()),使用局部事件,对两者进行了更好的封装,而不是扔在全局中。

Promise实现

基于上面的思想,我们可以实现一个简单的Promise:MyPromise

  1. class MyPromise {
  2. constructor (run) { // run 函数 (resolve) => any
  3. this.observerList = [];
  4. const notifyAll = value => this.observerList.forEach(callback => callback(value));
  5. run(notifyAll); // !!! 核心
  6. }
  7. subscribe (callback) {
  8. this.observerList.push(callback);
  9. }
  10. }
  11. //
  12. const p = new MyPromise(notifyAll => {
  13. fs.readFile("h.js", (err, data) => {
  14. notifyAll(data.toString()) // resolve
  15. })
  16. });
  17. p.subscribe(data => console.log(data)); // then

几行代码就实现了一个简单的Promise,而上面的代码也就是把观察者设计模式稍微改了改而已。

添加状态

当然还没结束,上面的MyPromise是有问题的。之前说了Promise是对异步任务的封装,可以看成最小异步单元(像回调一样),而异步结果也应该只有一个,即Promise中的resolve只能使用一次,相当于EventEmitter的once事件。而上面实现的MyPromise的notifyAll是可以用多次的(没有为什么),因此这就可以产生异步任务的结果可以不止一个的错误。因此解决方法就是加一个bool变量或者添加状态即pending态和fulfilled态(本质上和一个bool变量是一样的),当notifyAll调用一次后立马锁住notifyAll或者当pending态变为fulfilled态后再次调用notifyAll函数将不起作用。
为了和Promise对象一致,这里使用添加状态的方式(顺便把方法名给改了一下, notifyAll => resolve, subscribe => then)。

  1. const pending = "pending";
  2. const fulfilled = "fulfilled";
  3. class MyPromise {
  4. constructor (run) { // run 函数 (resolve) => any
  5. this.observerList = [];
  6. this.status = pending;
  7. const resolve = value => {
  8. if (this.status === pending) {
  9. this.status = fulfilled;
  10. this.observerList.forEach(callback => callback(value));
  11. }
  12. };
  13. run(resolve); // !!! 核心
  14. }
  15. then (callback) {
  16. this.observerList.push(callback);
  17. }
  18. }
  19. const p = new MyPromise(resolve => {
  20. setTimeout(() => {
  21. resolve("hello world");
  22. resolve("hello world2"); // 不好使了
  23. }, 1000);
  24. });
  25. p.then(value => console.log(value));

实现链式调用

貌似开始有点轮廓了,不过现在的MyPromise中的then可没有链式调用,接下来我们来实现then链,需要注意的在Promise中then方法返回的是一个新的Promise实例不是之前的Promise。由于then方法一直返回新的MyPromise对象,所以需要一个属性来保存唯一的异步结果。另一方面,在实现then方法依然是注册回调,但实现时需要考虑当前的状态,如果是pending态,我们需要在返回新的MyPromise的同时,将回调注册到队列中,如果是fulfilled态,那直接返回新的MyPromise对象,并将上一个MyPromise对象的结果给新的MyPromise对象。

  1. const pending = "pending";
  2. const fulfilled = "fulfilled";
  3. class MyPromise {
  4. constructor (run) { // run 函数 (resolve) => any
  5. this.resolvedCallback = [];
  6. this.status = pending;
  7. this.data = void 666; // 保存异步结果
  8. const resolve = value => {
  9. if (this.status === pending) {
  10. this.status = fulfilled;
  11. this.data = value; // 存一下结果
  12. this.resolvedCallback.forEach(callback => callback(this.data));
  13. }
  14. };
  15. run(resolve); // !!! 核心
  16. }
  17. then (onResolved) {
  18. // 这里需要对onResolved做一下处理,当onResolved不是函数时将它变成函数
  19. onResolved = typeof onResolved === "function" ? onResolved : value => value;
  20. switch (this.status) {
  21. case pending: {
  22. return new MyPromise(resolve => {
  23. this.resolvedCallback.push(value => { // 再包装
  24. const result = onResolved(value); // 需要判断一下then接的回调返回的是不是一个MyPromise对象
  25. if (result instanceof MyPromise) {
  26. result.then(resolve) // 如果是,直接使用result.then后的结果,毕竟Promise里面就需要这么做
  27. } else {
  28. resolve(result); // 感受一下闭包的伟大
  29. }
  30. })
  31. })
  32. }
  33. case fulfilled: {
  34. return new MyPromise(resolve => {
  35. const result = onResolved(this.data); // fulfilled态,this.data一定存在,其实这里就像map过程
  36. if (result instanceof MyPromise) {
  37. result.then(resolve)
  38. } else {
  39. resolve(result); // 闭包真伟大
  40. }
  41. })
  42. }
  43. }
  44. }
  45. }
  46. const p = new MyPromise(resolve => {
  47. setTimeout(() => {
  48. resolve("hello world");
  49. resolve("hello world2"); // 不好使了
  50. }, 1000);
  51. });
  52. p.then(value => value + "dpf")
  53. .then(value => value.toUpperCase())
  54. .then(console.log);

以上代码需要重点理解,毕竟理解了上面的代码,下面的就很容易了

错误处理

只有resolve和then的MyPromise对象已经完成。没有测试的库就是耍流氓,没有差错处理的代码也是耍流氓,所以错误处理还是很重要的。由于一个异步任务可能完不成或者中间会出错,这种情况必须得处理。因此我们需要加一个状态rejected来表示异步任务出错,并且使用rejectedCallback队列来存储reject发送的错误事件。(前方高能预警,面向try/catch编程开始了)

  1. const pending = "pending";
  2. const fulfilled = "fulfilled";
  3. const rejected = "rejected"; // 添加状态 rejected
  4. class MyPromise {
  5. constructor (run) { // run 函数 (resolve, reject) => any
  6. this.resolvedCallback = [];
  7. this.rejectedCallback = []; // 添加一个处理错误的队列
  8. this.status = pending;
  9. this.data = void 666; // 保存异步结果
  10. const resolve = value => {
  11. if (this.status === pending) {
  12. this.status = fulfilled;
  13. this.data = value;
  14. this.resolvedCallback.forEach(callback => callback(this.data));
  15. }
  16. };
  17. const reject = err => {
  18. if (this.status === pending) {
  19. this.status = rejected;
  20. this.data = err;
  21. this.rejectedCallback.forEach(callback => callback(this.data));
  22. }
  23. };
  24. try { // 对构造器里传入的函数进行try / catch
  25. run(resolve, reject); // !!! 核心
  26. } catch (e) {
  27. reject(e)
  28. }
  29. }
  30. then (onResolved, onRejected) { // 添加两个监听函数
  31. // 这里需要对onResolved做一下处理,当onResolved不是函数时将它变成函数
  32. onResolved = typeof onResolved === "function" ? onResolved : value => value;
  33. onRejected = typeof onRejected === "function" ? onRejected : err => { throw err };
  34. switch (this.status) {
  35. case pending: {
  36. return new MyPromise((resolve, reject) => {
  37. this.resolvedCallback.push(value => {
  38. try { // 对整个onResolved进行try / catch
  39. const result = onResolved(value);
  40. if (result instanceof MyPromise) {
  41. result.then(resolve, reject)
  42. } else {
  43. resolve(result);
  44. }
  45. } catch (e) {
  46. reject(e) // 捕获异常,将异常发布
  47. }
  48. });
  49. this.rejectedCallback.push(err => {
  50. try { // 对整个onRejected进行try / catch
  51. const result = onRejected(err);
  52. if (result instanceof MyPromise) {
  53. result.then(resolve, reject)
  54. } else {
  55. reject(err)
  56. }
  57. } catch (e) {
  58. reject(err) // 捕获异常,将异常发布
  59. }
  60. })
  61. })
  62. }
  63. case fulfilled: {
  64. return new MyPromise((resolve, reject) => {
  65. try { // 对整个过程进行try / catch
  66. const result = onResolved(this.data);
  67. if (result instanceof MyPromise) {
  68. result.then(resolve, reject)
  69. } else {
  70. resolve(result);
  71. }
  72. } catch (e) {
  73. reject(e) // 捕获异常,将异常发布
  74. }
  75. })
  76. }
  77. case rejected: {
  78. return new MyPromise((resolve, reject) => {
  79. try { // 对整个过程进行try / catch
  80. const result = onRejected(this.data);
  81. if (result instanceof MyPromise) {
  82. result.then(resolve, reject)
  83. } else {
  84. reject(result)
  85. }
  86. } catch (e) {
  87. reject(e) // 捕获异常,将异常发布
  88. }
  89. })
  90. }
  91. }
  92. }
  93. }
  94. const p = new MyPromise((resolve, reject) => {
  95. setTimeout(() => {
  96. reject(new Error("error"));
  97. resolve("hello world"); // 不好使了
  98. resolve("hello world2"); // 不好使了
  99. }, 1000);
  100. });
  101. p.then(value => value + "dpf")
  102. .then(console.log)
  103. .then(() => {}, err => console.log(err));

可以看出then方法的实现比较复杂,但这是一个核心的方法,实现了这个后面的其他方法就很好实现了,下面给出MyPromise的每一个方法的实现。

catch实现

这个实现非常简单

  1. catch (onRejected) {
  2. return this.then(void 666, onRejected)
  3. }

静态方法MyPromise.resolve

  1. static resolve(p) {
  2. if (p instanceof MyPromise) {
  3. return p.then()
  4. }
  5. return new MyPromise((resolve, reject) => {
  6. resolve(p)
  7. })
  8. }

静态方法MyPromise.reject

  1. static reject(p) {
  2. if (p instanceof MyPromise) {
  3. return p.catch()
  4. }
  5. return new MyPromise((resolve, reject) => {
  6. reject(p)
  7. })
  8. }

静态方法MyPromise.all

  1. static all (promises) {
  2. return new MyPromise((resolve, reject) => {
  3. try {
  4. let count = 0,
  5. len = promises.length,
  6. value = [];
  7. for (let promise of promises) {
  8. MyPromise.resolve(promise).then(v => {
  9. count ++;
  10. value.push(v);
  11. if (count === len) {
  12. resolve(value)
  13. }
  14. })
  15. }
  16. } catch (e) {
  17. reject(e)
  18. }
  19. });
  20. }

静态方法MyPromise.race

  1. static race(promises) {
  2. return new MyPromise((resolve, reject) => {
  3. try {
  4. for (let promise of promises) {
  5. MyPromise.resolve(promise).then(resolve)
  6. }
  7. } catch (e) {
  8. reject(e)
  9. }
  10. })
  11. }

完整的MyPromise代码实现

  1. const pending = "pending";
  2. const fulfilled = "fulfilled";
  3. const rejected = "rejected"; // 添加状态 rejected
  4. class MyPromise {
  5. constructor (run) { // run 函数 (resolve, reject) => any
  6. this.resolvedCallback = [];
  7. this.rejectedCallback = []; // 添加一个处理错误的队列
  8. this.status = pending;
  9. this.data = void 666; // 保存异步结果
  10. const resolve = value => {
  11. if (this.status === pending) {
  12. this.status = fulfilled;
  13. this.data = value;
  14. this.resolvedCallback.forEach(callback => callback(this.data));
  15. }
  16. };
  17. const reject = err => {
  18. if (this.status === pending) {
  19. this.status = rejected;
  20. this.data = err;
  21. this.rejectedCallback.forEach(callback => callback(this.data));
  22. }
  23. };
  24. try { // 对构造器里传入的函数进行try / catch
  25. run(resolve, reject); // !!! 核心
  26. } catch (e) {
  27. reject(e)
  28. }
  29. }
  30. static resolve (p) {
  31. if (p instanceof MyPromise) {
  32. return p.then()
  33. }
  34. return new MyPromise((resolve, reject) => {
  35. resolve(p)
  36. })
  37. }
  38. static reject (p) {
  39. if (p instanceof MyPromise) {
  40. return p.catch()
  41. }
  42. return new MyPromise((resolve, reject) => {
  43. reject(p)
  44. })
  45. }
  46. static all (promises) {
  47. return new MyPromise((resolve, reject) => {
  48. try {
  49. let count = 0,
  50. len = promises.length,
  51. value = [];
  52. for (let promise of promises) {
  53. MyPromise.resolve(promise).then(v => {
  54. count ++;
  55. value.push(v);
  56. if (count === len) {
  57. resolve(value)
  58. }
  59. })
  60. }
  61. } catch (e) {
  62. reject(e)
  63. }
  64. });
  65. }
  66. static race(promises) {
  67. return new MyPromise((resolve, reject) => {
  68. try {
  69. for (let promise of promises) {
  70. MyPromise.resolve(promise).then(resolve)
  71. }
  72. } catch (e) {
  73. reject(e)
  74. }
  75. })
  76. }
  77. catch (onRejected) {
  78. return this.then(void 666, onRejected)
  79. }
  80. then (onResolved, onRejected) { // 添加两个监听函数
  81. // 这里需要对onResolved做一下处理,当onResolved不是函数时将它变成函数
  82. onResolved = typeof onResolved === "function" ? onResolved : value => value;
  83. onRejected = typeof onRejected === "function" ? onRejected : err => { throw err };
  84. switch (this.status) {
  85. case pending: {
  86. return new MyPromise((resolve, reject) => {
  87. this.resolvedCallback.push(value => {
  88. try { // 对整个onResolved进行try / catch
  89. const result = onResolved(value);
  90. if (result instanceof MyPromise) {
  91. result.then(resolve, reject)
  92. } else {
  93. resolve(result);
  94. }
  95. } catch (e) {
  96. reject(e)
  97. }
  98. });
  99. this.rejectedCallback.push(err => {
  100. try { // 对整个onRejected进行try / catch
  101. const result = onRejected(err);
  102. if (result instanceof MyPromise) {
  103. result.then(resolve, reject)
  104. } else {
  105. reject(err)
  106. }
  107. } catch (e) {
  108. reject(err)
  109. }
  110. })
  111. })
  112. }
  113. case fulfilled: {
  114. return new MyPromise((resolve, reject) => {
  115. try { // 对整个过程进行try / catch
  116. const result = onResolved(this.data);
  117. if (result instanceof MyPromise) {
  118. result.then(resolve, reject)
  119. } else {
  120. resolve(result); // emit
  121. }
  122. } catch (e) {
  123. reject(e)
  124. }
  125. })
  126. }
  127. case rejected: {
  128. return new MyPromise((resolve, reject) => {
  129. try { // 对整个过程进行try / catch
  130. const result = onRejected(this.data);
  131. if (result instanceof MyPromise) {
  132. result.then(resolve, reject)
  133. } else {
  134. reject(result)
  135. }
  136. } catch (e) {
  137. reject(e)
  138. }
  139. })
  140. }
  141. }
  142. }
  143. }

总结

本文想要从发布-订阅和观察者模式分析Promise的实现,先从异步编程的演变说起,回调函数到发布-订阅和观察者设计模式,然后发现Promise和观察者设计模式比较类似,所以先从这个角度分析了Promise的实现,当然Promise的功能远不如此,所以本文分析了Promise的常用方法的实现原理。Promise的出现改变了传统的异步编程方式,使JavaScript在进行异步编程时更加灵活,代码更加可维护、可阅读。所以作为一个有追求的前端,必须要对Promise的实现有一定的理解。