本文是一起学习造轮子系列的第一篇,本篇我们将从零开始写一个符合Promises/A+规范的promise,本系列文章将会选取一些前端比较经典的轮子进行源码分析,并且从零开始逐步实现,本系列将会学习Promises/A+,Redux,react-redux,vue,dom-diff,webpack,babel,kao,express,async/await,jquery,Lodash,requirejs,lib-flexible等前端经典轮子的实现方式,每一章源码都托管在github上,欢迎关注~

相关系列文章:

一起学习造轮子(一):从零开始写一个符合Promises/A+规范的promise

一起学习造轮子(二):从零开始写一个Redux

一起学习造轮子(三):从零开始写一个React-Redux

本系列github仓库:

一起学习造轮子系列github(欢迎star~)

前言

Promise 是异步编程的一种解决方案,比传统的解决方案回调函数和事件更合理更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。本篇不注重讲解promise的用法,关于用法,可以看阮一峰老师的ECMAScript 6系列里面的Promise部分:

ECMAScript 6 : Promise对象

本篇主要讲解如何从零开始一步步的实现promise各项特性及功能,最终使其符合Promises/A+规范,因为讲解较细,所以文章略长。

另外,每一步的项目源码都在github上,可以对照参考,每一步都有对应的项目代码及测试代码,喜欢的话,欢迎给个star~

项目地址:本文代码的github仓库

开始

本文promise里用到的异步操作的示例都是使用的node里面的fs.readFile方法,在浏览器端可以使用setTimeout方法进行模拟异步操作。

一. 基础版本

目标

  1. 可以创建promise对象实例。
  2. promise实例传入的异步方法执行成功就执行注册的成功回调函数,失败就执行注册的失败回调函数。

实现

  1. function MyPromise(fn) {
  2. let self = this; // 缓存当前promise实例
  3. self.value = null; //成功时的值
  4. self.error = null; //失败时的原因
  5. self.onFulfilled = null; //成功的回调函数
  6. self.onRejected = null; //失败的回调函数
  7. function resolve(value) {
  8. self.value = value;
  9. self.onFulfilled(self.value);//resolve时执行成功回调
  10. }
  11. function reject(error) {
  12. self.error = error;
  13. self.onRejected(self.error)//reject时执行失败回调
  14. }
  15. fn(resolve, reject);
  16. }
  17. MyPromise.prototype.then = function(onFulfilled, onRejected) {
  18. //在这里给promise实例注册成功和失败回调
  19. this.onFulfilled = onFulfilled;
  20. this.onRejected = onRejected;
  21. }
  22. module.exports = MyPromise

代码很短,逻辑也非常清晰,在then中注册了这个promise实例的成功回调和失败回调,当promise reslove时,就把异步执行结果赋值给promise实例的value,并把这个值传入成功回调中执行,失败就把异步执行失败原因赋值给promise实例的error,并把这个值传入失败回调并执行。

本节代码

基础版本代码

二. 支持同步任务

我们知道,我们在使用es6 的promise时,可以传入一个异步任务,也可以传入一个同步任务,但是我们的上面基础版代码并不支持同步任务,如果我们这样写就会报错:

  1. let promise = new Promise((resolve, reject) => {
  2. resolve("同步任务执行")
  3. });

为什么呢?因为是同步任务,所以当我们的promise实例reslove时,它的then方法还没执行到,所以回调函数还没注册上,这时reslove中调用成功回调肯定会报错的。

目标

使promise支持同步方法

实现

  1. function resolve(value) {
  2. //利用setTimeout特性将具体执行放到then之后
  3. setTimeout(() => {
  4. self.value = value;
  5. self.onFulfilled(self.value)
  6. })
  7. }
  8. function reject(error) {
  9. setTimeout(() => {
  10. self.error = error;
  11. self.onRejected(self.error)
  12. })
  13. }

实现很简单,就是在reslove和reject里面用setTimeout进行包裹,使其到then方法执行之后再去执行,这样我们就让promise支持传入同步方法,另外,关于这一点,Promise/A+规范里也明确要求了这一点。

2.2.4 onFulfilled or onRejected must not be called until the execution context stack contains only platform code.

本节代码

支持同步任务代码

三. 支持三种状态

我们知道在使用promise时,promise有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。另外,promise一旦状态改变,就不会再变,任何时候都可以得到这个结果promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,如果改变已经发生了,你再对promise对象添加回调函数,也会立即得到这个结果。

目标

  1. 实现promise的三种状态。
  2. 实现promise对象的状态改变,改变只有两种可能:从pending变为fulfilled和从pending变为rejected。
  3. 实现一旦promise状态改变,再对promise对象添加回调函数,也会立即得到这个结果。

实现

  1. //定义三种状态
  2. const PENDING = "pending";
  3. const FULFILLED = "fulfilled";
  4. const REJECTED = "rejected";
  5. function MyPromise(fn) {
  6. let self = this;
  7. self.value = null;
  8. self.error = null;
  9. self.status = PENDING;
  10. self.onFulfilled = null;
  11. self.onRejected = null;
  12. function resolve(value) {
  13. //如果状态是pending才去修改状态为fulfilled并执行成功逻辑
  14. if (self.status === PENDING) {
  15. setTimeout(function() {
  16. self.status = FULFILLED;
  17. self.value = value;
  18. self.onFulfilled(self.value);
  19. })
  20. }
  21. }
  22. function reject(error) {
  23. //如果状态是pending才去修改状态为rejected并执行失败逻辑
  24. if (self.status === PENDING) {
  25. setTimeout(function() {
  26. self.status = REJECTED;
  27. self.error = error;
  28. self.onRejected(self.error);
  29. })
  30. }
  31. }
  32. fn(resolve, reject);
  33. }
  34. MyPromise.prototype.then = function(onFulfilled, onRejected) {
  35. if (this.status === PENDING) {
  36. this.onFulfilled = onFulfilled;
  37. this.onRejected = onRejected;
  38. } else if (this.status === FULFILLED) {
  39. //如果状态是fulfilled,直接执行成功回调,并将成功值传入
  40. onFulfilled(this.value)
  41. } else {
  42. //如果状态是rejected,直接执行失败回调,并将失败原因传入
  43. onRejected(this.error)
  44. }
  45. return this;
  46. }
  47. module.exports = MyPromise

首先,我们建立了三种状态"pending","fulfilled","rejected",然后我们在reslove和reject中做判断,只有状态是pending时,才去改变promise的状态,并执行相应操作,另外,我们在then中判断,如果这个promise已经变为"fulfilled"或"rejected"就立刻执行它的回调,并把结果传入。

本节代码

支持三种状态代码

四. 支持链式操作

我们平时写promise一般都是对应的一组流程化的操作,如这样:

promise.then(f1).then(f2).then(f3)

但是我们之前的版本最多只能注册一个回调,这一节我们就来实现链式操作。

目标

使promise支持链式操作

实现

想支持链式操作,其实很简单,首先存储回调时要改为使用数组

  1. self.onFulfilledCallbacks = [];
  2. self.onRejectedCallbacks = [];

当然执行回调时,也要改成遍历回调数组执行回调函数


  1. self.onFulfilledCallbacks.forEach((callback) => callback(self.value));

最后,then方法也要改一下,只需要在最后一行加一个return this即可,这其实和jQuery链式操作的原理一致,每次调用完方法都返回自身实例,后面的方法也是实例的方法,所以可以继续执行。

  1. MyPromise.prototype.then = function(onFulfilled, onRejected) {
  2. if (this.status === PENDING) {
  3. this.onFulfilledCallbacks.push(onFulfilled);
  4. this.onRejectedCallbacks.push(onRejected);
  5. } else if (this.status === FULFILLED) {
  6. onFulfilled(this.value)
  7. } else {
  8. onRejected(this.error)
  9. }
  10. return this;
  11. }

本节代码

支持链式操作代码

五. 支持串行异步任务

我们上一节实现了链式调用,但是目前then方法里只能传入同步任务,但是我们平常用promise,then方法里一般是异步任务,因为我们用promise主要用来解决一组流程化的异步操作,如下面这样的调取接口获取用户id后,再根据用户id调取接口获取用户余额,获取用户id和获取用户余额都需要调用接口,所以都是异步任务,如何使promise支持串行异步操作呢?

  1. getUserId()
  2. .then(getUserBalanceById)
  3. .then(function (balance) {
  4. // do sth
  5. }, function (error) {
  6. console.log(error);
  7. });

目标

使promise支持串行异步操作

实现

这里为方便讲解我们引入一个常见场景:用promise顺序读取文件内容,场景代码如下:

  1. let p = new Promise((resolve, reject) => {
  2. fs.readFile('../file/1.txt', "utf8", function(err, data) {
  3. err ? reject(err) : resolve(data)
  4. });
  5. });
  6. let f1 = function(data) {
  7. console.log(data)
  8. return new Promise((resolve, reject) => {
  9. fs.readFile('../file/2.txt', "utf8", function(err, data) {
  10. err ? reject(err) : resolve(data)
  11. });
  12. });
  13. }
  14. let f2 = function(data) {
  15. console.log(data)
  16. return new Promise((resolve, reject) => {
  17. fs.readFile('../file/3.txt', "utf8", function(err, data) {
  18. err ? reject(err) : resolve(data)
  19. });
  20. });
  21. }
  22. let f3 = function(data) {
  23. console.log(data);
  24. }
  25. let errorLog = function(error) {
  26. console.log(error)
  27. }
  28. p.then(f1).then(f2).then(f3).catch(errorLog)
  29. //会依次输出
  30. //this is 1.txt
  31. //this is 2.txt
  32. //this is 3.txt

上面场景,我们读取完1.txt后并打印1.txt内容,再去读取2.txt并打印2.txt内容,再去读取3.txt并打印3.txt内容,而读取文件都是异步操作,所以都是返回一个promise,我们上一节实现的promise可以实现执行完异步操作后执行后续回调,但是本节的回调读取文件内容操作并不是同步的,而是异步的,所以当读取完1.txt后,执行它回调onFulfilledCallbacks里面的f1,f2,f3时,异步操作还没有完成,所以我们本想得到这样的输出:

  1. this is 1.txt
  2. this is 2.txt
  3. this is 3.txt

但是实际上却会输出

  1. this is 1.txt
  2. this is 1.txt
  3. this is 1.txt

所以要想实现异步操作串行,我们不能将回调函数都注册在初始promise的onFulfilledCallbacks里面,而要将每个回调函数注册在对应的异步操作promise的onFulfilledCallbacks里面,用读取文件的场景来举例,f1要在p的onFulfilledCallbacks里面,而f2应该在f1里面return的那个Promise的onFulfilledCallbacks里面,因为只有这样才能实现读取完2.txt后才去打印2.txt的结果。

但是,我们平常写promise一般都是这样写的: promise.then(f1).then(f2).then(f3),一开始所有流程我们就指定好了,而不是在f1里面才去注册f1的回调,f2里面才去注册f2的回调。

如何既能保持这种链式写法的同时又能使异步操作衔接执行呢?我们其实让then方法最后不再返回自身实例,而是返回一个新的promise即可,我们可以叫它bridgePromise,它最大的作用就是衔接后续操作,我们看下具体实现代码:

  1. MyPromise.prototype.then = function(onFulfilled, onRejected) {
  2. const self = this;
  3. let bridgePromise;
  4. //防止使用者不传成功或失败回调函数,所以成功失败回调都给了默认回调函数
  5. onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value;
  6. onRejected = typeof onRejected === "function" ? onRejected : error => { throw error };
  7. if (self.status === FULFILLED) {
  8. return bridgePromise = new MyPromise((resolve, reject) => {
  9. setTimeout(() => {
  10. try {
  11. let x = onFulfilled(self.value);
  12. resolvePromise(bridgePromise, x, resolve, reject);
  13. } catch (e) {
  14. reject(e);
  15. }
  16. });
  17. })
  18. }
  19. if (self.status === REJECTED) {
  20. return bridgePromise = new MyPromise((resolve, reject) => {
  21. setTimeout(() => {
  22. try {
  23. let x = onRejected(self.error);
  24. resolvePromise(bridgePromise, x, resolve, reject);
  25. } catch (e) {
  26. reject(e);
  27. }
  28. });
  29. });
  30. }
  31. if (self.status === PENDING) {
  32. return bridgePromise = new MyPromise((resolve, reject) => {
  33. self.onFulfilledCallbacks.push((value) => {
  34. try {
  35. let x = onFulfilled(value);
  36. resolvePromise(bridgePromise, x, resolve, reject);
  37. } catch (e) {
  38. reject(e);
  39. }
  40. });
  41. self.onRejectedCallbacks.push((error) => {
  42. try {
  43. let x = onRejected(error);
  44. resolvePromise(bridgePromise, x, resolve, reject);
  45. } catch (e) {
  46. reject(e);
  47. }
  48. });
  49. });
  50. }
  51. }
  52. //catch方法其实是个语法糖,就是只传onRejected不传onFulfilled的then方法
  53. MyPromise.prototype.catch = function(onRejected) {
  54. return this.then(null, onRejected);
  55. }
  56. //用来解析回调函数的返回值x,x可能是普通值也可能是个promise对象
  57. function resolvePromise(bridgePromise, x, resolve, reject) {
  58. //如果x是一个promise
  59. if (x instanceof MyPromise) {
  60. //如果这个promise是pending状态,就在它的then方法里继续执行resolvePromise解析它的结果,直到返回值不是一个pending状态的promise为止
  61. if (x.status === PENDING) {
  62. x.then(y => {
  63. resolvePromise(bridgePromise, y, resolve, reject);
  64. }, error => {
  65. reject(error);
  66. });
  67. } else {
  68. x.then(resolve, reject);
  69. }
  70. //如果x是一个普通值,就让bridgePromise的状态fulfilled,并把这个值传递下去
  71. } else {
  72. resolve(x);
  73. }
  74. }

首先,为防止使用者不传成功回调函数或不失败回调函数,我们给了默认回调函数,然后无论当前promise是什么状态,我们都返回一个bridgePromise用来衔接后续操作。

另外执行回调函数时,因为回调函数既可能会返回一个异步的promise也可能会返回一个同步结果,所以我们把直接把回调函数的结果托管给bridgePromise,使用resolvePromise方法来解析回调函数的结果,如果回调函数返回一个promise并且状态还是pending,就在这个promise的then方法中继续解析这个promise reslove传过来的值,如果值还是pending状态的promise就继续解析,直到不是一个异步promise,而是一个正常值就使用bridgePromise的reslove方法将bridgePromise的状态改为fulfilled,并调用onFulfilledCallbacks回调数组中的方法,将该值传入,到此异步操作就衔接上了。

这里很抽象,我们还是以文件顺序读取的场景画一张图解释一下流程:

当执行p.then(f1).then(f2).then(f3)时:

  1. 先执行p.then(f1)返回了一个bridgePromise(p2),并在p的onFulfilledCallbacks回调列表中放入一个回调函数,回调函数负责执行f1并且更新p2的状态.
  2. 然后.then(f2)时返回了一个bridgePromise(p3),这里注意其实是p2.then(f2),因为p.then(f1)时返回了p2。此时在p2的onFulfilledCallbacks回调列表中放入一个回调函数,回调函数负责执行f2并且更新p3的状态.
  3. 然后.then(f3)时返回了一个bridgePromise(p4),并在p3的onFulfilledCallbacks回调列表中放入一个回调函数,回调函数负责执行f3并且更新p4的状态.

    到此,回调关系注册完了,如图所示:

  4. 然后过了一段时间,p里面的异步操作执行完了,读取到了1.txt的内容,开始执行p的回调函数,回调函数执行f1,打印出1.txt的内容“this is 1.txt”,并将f1的返回值放到resolvePromise中开始解析。resolvePromise一看传入了一个promise对象,promise是异步的啊,得等着呢,于是就在这个promise对象的then方法中继续resolvePromise这个promise对象resolve的结果,一看不是promise对象了,而是一个具体值“this is 2.txt”,于是调用bridgePromise(p2)的reslove方法将bridgePromise(p2)的状态更新为fulfilled,并将“this is 2.txt”传入p2的回调函数中去执行。
  5. p2的回调开始执行,f2拿到传过来的“this is 2.txt”参数开始执行,打印出2.txt的内容,并将f2的返回值放到resolvePromise中开始解析,resolvePromise一看传入了一个promise对象,promise是异步的啊,又得等着呢........后续操作就是不断的重复4,5步直到结束。

到此,reslove这一条线已经我们已经走通,让我们看看reject这一条线,reject其实处理起来很简单:

  1. 首先执行fn及执行注册的回调时都用try-catch包裹,无论哪里有异常都会进入reject分支。
  2. 一旦代码进入reject分支直接将bridge promise设为rejected状态,于是后续都会走reject这个分支,另外如果不传异常处理的onRejected函数,默认就是使用throw error将错误一直往后抛,达到了错误冒泡的目的。
  3. 最后可以实现一个catch函数用来接收错误。
  1. MyPromise.prototype.catch = function(onRejected) {
  2. return this.then(null, onRejected);
  3. }

到此,我们已经可以愉快的使用promise.then(f1).then(f2).then(f3).catch(errorLog)来顺序读取文件内容了。

本节代码

支持串行异步任务代码

六. 达到Promises/A+规范

其实,到支持串行异步任务这一节,我们写的promise在功能上已经基本齐全了,但是还不太规范,比如说一些其他情况的判断等等,这一节我们就比着Promises/A+的规范打磨一下我们写的promise。如果只是想学习promise的核心实现的,这一节看不懂也没关系,因为这一节并没有增加promise的功能,只是使promise更加规范,更加健壮。

目标

使promise达到Promises/A+规范,通过promises-aplus-tests的完整测试

实现

首先来可以了解一下Promises/A+规范:

Promises/A+规范原版

Promises/A+规范中文版

相比上一节代码,本节代码除了在resolvePromise函数里增加了几个其他情况的判断外,其他函数都没有修改。完整promise代码如下:

  1. const PENDING = "pending";
  2. const FULFILLED = "fulfilled";
  3. const REJECTED = "rejected";
  4. function MyPromise(fn) {
  5. const self = this;
  6. self.value = null;
  7. self.error = null;
  8. self.status = PENDING;
  9. self.onFulfilledCallbacks = [];
  10. self.onRejectedCallbacks = [];
  11. function resolve(value) {
  12. if (value instanceof MyPromise) {
  13. return value.then(resolve, reject);
  14. }
  15. if (self.status === PENDING) {
  16. setTimeout(() => {
  17. self.status = FULFILLED;
  18. self.value = value;
  19. self.onFulfilledCallbacks.forEach((callback) => callback(self.value));
  20. }, 0)
  21. }
  22. }
  23. function reject(error) {
  24. if (self.status === PENDING) {
  25. setTimeout(function() {
  26. self.status = REJECTED;
  27. self.error = error;
  28. self.onRejectedCallbacks.forEach((callback) => callback(self.error));
  29. }, 0)
  30. }
  31. }
  32. try {
  33. fn(resolve, reject);
  34. } catch (e) {
  35. reject(e);
  36. }
  37. }
  38. function resolvePromise(bridgepromise, x, resolve, reject) {
  39. //2.3.1规范,避免循环引用
  40. if (bridgepromise === x) {
  41. return reject(new TypeError('Circular reference'));
  42. }
  43. let called = false;
  44. //这个判断分支其实已经可以删除,用下面那个分支代替,因为promise也是一个thenable对象
  45. if (x instanceof MyPromise) {
  46. if (x.status === PENDING) {
  47. x.then(y => {
  48. resolvePromise(bridgepromise, y, resolve, reject);
  49. }, error => {
  50. reject(error);
  51. });
  52. } else {
  53. x.then(resolve, reject);
  54. }
  55. // 2.3.3规范,如果 x 为对象或者函数
  56. } else if (x != null && ((typeof x === 'object') || (typeof x === 'function'))) {
  57. try {
  58. // 是否是thenable对象(具有then方法的对象/函数)
  59. //2.3.3.1 将 then 赋为 x.then
  60. let then = x.then;
  61. if (typeof then === 'function') {
  62. //2.3.3.3 如果 then 是一个函数,以x为this调用then函数,且第一个参数是resolvePromise,第二个参数是rejectPromise
  63. then.call(x, y => {
  64. if (called) return;
  65. called = true;
  66. resolvePromise(bridgepromise, y, resolve, reject);
  67. }, error => {
  68. if (called) return;
  69. called = true;
  70. reject(error);
  71. })
  72. } else {
  73. //2.3.3.4 如果 then不是一个函数,则 以x为值fulfill promise。
  74. resolve(x);
  75. }
  76. } catch (e) {
  77. //2.3.3.2 如果在取x.then值时抛出了异常,则以这个异常做为原因将promise拒绝。
  78. if (called) return;
  79. called = true;
  80. reject(e);
  81. }
  82. } else {
  83. resolve(x);
  84. }
  85. }
  86. MyPromise.prototype.then = function(onFulfilled, onRejected) {
  87. const self = this;
  88. let bridgePromise;
  89. onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value;
  90. onRejected = typeof onRejected === "function" ? onRejected : error => { throw error };
  91. if (self.status === FULFILLED) {
  92. return bridgePromise = new MyPromise((resolve, reject) => {
  93. setTimeout(() => {
  94. try {
  95. let x = onFulfilled(self.value);
  96. resolvePromise(bridgePromise, x, resolve, reject);
  97. } catch (e) {
  98. reject(e);
  99. }
  100. }, 0);
  101. })
  102. }
  103. if (self.status === REJECTED) {
  104. return bridgePromise = new MyPromise((resolve, reject) => {
  105. setTimeout(() => {
  106. try {
  107. let x = onRejected(self.error);
  108. resolvePromise(bridgePromise, x, resolve, reject);
  109. } catch (e) {
  110. reject(e);
  111. }
  112. }, 0);
  113. });
  114. }
  115. if (self.status === PENDING) {
  116. return bridgePromise = new MyPromise((resolve, reject) => {
  117. self.onFulfilledCallbacks.push((value) => {
  118. try {
  119. let x = onFulfilled(value);
  120. resolvePromise(bridgePromise, x, resolve, reject);
  121. } catch (e) {
  122. reject(e);
  123. }
  124. });
  125. self.onRejectedCallbacks.push((error) => {
  126. try {
  127. let x = onRejected(error);
  128. resolvePromise(bridgePromise, x, resolve, reject);
  129. } catch (e) {
  130. reject(e);
  131. }
  132. });
  133. });
  134. }
  135. }
  136. MyPromise.prototype.catch = function(onRejected) {
  137. return this.then(null, onRejected);
  138. }
  139. // 执行测试用例需要用到的代码
  140. MyPromise.deferred = function() {
  141. let defer = {};
  142. defer.promise = new MyPromise((resolve, reject) => {
  143. defer.resolve = resolve;
  144. defer.reject = reject;
  145. });
  146. return defer;
  147. }
  148. try {
  149. module.exports = MyPromise
  150. } catch (e) {}

我们可以先跑一下测试,需要安装一下测试插件,然后执行测试,测试时注意在加上上面最后的那几行代码才能执行测试用例。

  1. 1.npm i -g promises-aplus-tests
  2. 2.promises-aplus-tests mypromise.js

运行测试用例可以看到,我们上面写的promise代码通过了完整的Promises/A+规范测试。



先撒花高兴一下~✿✿ヽ(°▽°)ノ✿

然后开始分析我们这一节的代码,我们主要在resolvePromise里加了额外的两个判断,第一个是x和bridgePromise是指向相同值时,报出循环引用的错误,使promise符合2.3.1规范,然后我们增加了一个x 为对象或者函数的判断,这一条判断主要对应2.3.3规范,中文规范如图:



这一条标准对应的其实是thenable对象,什么是thenable对象,只要有then方法就是thenable对象,然后我们实现的时候照着规范实现就可以了。

  1. else if (x != null && ((typeof x === 'object') || (typeof x === 'function'))) {
  2. try {
  3. // 是否是thenable对象(具有then方法的对象/函数)
  4. //2.3.3.1 将 then 赋为 x.then
  5. let then = x.then;
  6. if (typeof then === 'function') {
  7. //2.3.3.3 如果 then 是一个函数,以x为this调用then函数,且第一个参数是resolvePromise,第二个参数是rejectPromise
  8. then.call(x, y => {
  9. if (called) return;
  10. called = true;
  11. resolvePromise(bridgepromise, y, resolve, reject);
  12. }, error => {
  13. if (called) return;
  14. called = true;
  15. reject(error);
  16. })
  17. } else {
  18. //2.3.3.4 如果 then不是一个函数,则以x为值fulfill promise。
  19. resolve(x);
  20. }
  21. } catch (e) {
  22. //2.3.3.2 如果在取x.then值时抛出了异常,则以这个异常做为原因将promise拒绝。
  23. if (called) return;
  24. called = true;
  25. reject(e);
  26. }
  27. }

再写完这个分支的代码后,其实我们已经可以删除if (x instanceof MyPromise) {}这个分支的代码,因为promise也是一个thenable对象,完全可以使用上述代码兼容代替。另外,本节代码很多重复代码可以封装优化一下,但是为了看得清晰,并没有进行抽象封装,大家如果觉得重复代码太多的话,可以自行抽象封装。

本节代码

达到Promises/A+规范代码

七. 实现 promise 的all,race,resolve,reject方法

上一节我们已经实现了一个符合Promises/A+规范的promise,本节我们把一些es6 promise里的常用方法实现一下。

目标

实现es6 promise的all,race,resolve,reject方法

实现

我们还是在之前的基础上继续往下写:

  1. MyPromise.all = function(promises) {
  2. return new MyPromise(function(resolve, reject) {
  3. let result = [];
  4. let count = 0;
  5. for (let i = 0; i < promises.length; i++) {
  6. promises[i].then(function(data) {
  7. result[i] = data;
  8. if (++count == promises.length) {
  9. resolve(result);
  10. }
  11. }, function(error) {
  12. reject(error);
  13. });
  14. }
  15. });
  16. }
  17. MyPromise.race = function(promises) {
  18. return new MyPromise(function(resolve, reject) {
  19. for (let i = 0; i < promises.length; i++) {
  20. promises[i].then(function(data) {
  21. resolve(data);
  22. }, function(error) {
  23. reject(error);
  24. });
  25. }
  26. });
  27. }
  28. MyPromise.resolve = function(value) {
  29. return new MyPromise(resolve => {
  30. resolve(value);
  31. });
  32. }
  33. MyPromise.reject = function(error) {
  34. return new MyPromise((resolve, reject) => {
  35. reject(error);
  36. });
  37. }

其实前几节把promise的主线逻辑实现后,这些方法都不难实现,all的原理就是返回一个promise,在这个promise中给所有传入的promise的then方法中都注册上回调,回调成功了就把值放到结果数组中,所有回调都成功了就让返回的这个promise去reslove,把结果数组返回出去,race和all大同小异,只不过它不会等所有promise都成功,而是谁快就把谁返回出去,resolve和reject的逻辑也很简单,看一下就明白了。

本节代码

实现all,race,resolve,reject方法代码

八. 实现 promiseify 方法

其实到上一节为止,promise的方法已经都讲完了,这一节讲一个著名promise库bluebird里面的方法promiseify,因为这个方法很常用而且以前面试还被问过。promiseify有什么作用呢?它的作用就是将异步回调函数api转换为promise形式,比如下面这个,对fs.readFile 执行promiseify后,就可以直接用promise的方式去调用读取文件的方法了,是不是很强大。

  1. let Promise = require('./bluebird');
  2. let fs = require("fs");
  3. var readFile = Promise.promisify(fs.readFile);
  4. readFile("1.txt", "utf8").then(function(data) {
  5. console.log(data);
  6. })

目标

实现bluebird的promiseify方法

实现

  1. MyPromise.promisify = function(fn) {
  2. return function() {
  3. var args = Array.from(arguments);
  4. return new MyPromise(function(resolve, reject) {
  5. fn.apply(null, args.concat(function(err) {
  6. err ? reject(err) : resolve(arguments[1])
  7. }));
  8. })
  9. }
  10. }

虽然方法很强大,但是实现起来并没有很难,想在外边直接调用promise的方法那就返回一个promise呗,内部将原来参数后面拼接一个回调函数参数,在回调函数里执行这个promise的reslove方法把结果传出去,promiseify就实现了。

本节代码

实现promiseify方法

最后

不知不觉写了这么多了,大家如果觉得还可以就给个赞呗,另外每一节的代码都托管到了github上,大家可以对照看那一节的promise实现代码及测试代码,也顺便求个star~

项目地址:本文代码的github仓库

另外,实现一个符合Promises/A+规范的promise不止本文一种实现方式,本文只是选取了一种比较通俗易懂的实现方式作为讲解,大家也可以用自己的方式去实现一个符合Promises/A+规范的promise。

一起学习造轮子(一):从零开始写一个符合Promises/A+规范的promise的更多相关文章

  1. 一起学习造轮子(二):从零开始写一个Redux

    本文是一起学习造轮子系列的第二篇,本篇我们将从零开始写一个小巧完整的Redux,本系列文章将会选取一些前端比较经典的轮子进行源码分析,并且从零开始逐步实现,本系列将会学习Promises/A+,Red ...

  2. 一起学习造轮子(三):从零开始写一个React-Redux

    本文是一起学习造轮子系列的第三篇,本篇我们将从零开始写一个React-Redux,本系列文章将会选取一些前端比较经典的轮子进行源码分析,并且从零开始逐步实现,本系列将会学习Promises/A+,Re ...

  3. 深入浅出React Native 3: 从零开始写一个Hello World

    这是深入浅出React Native的第三篇文章. 1. 环境配置 2. 我的第一个应用 将index.ios.js中的代码全部删掉,为什么要删掉呢?因为我们准备从零开始写一个应用~学习技术最好的方式 ...

  4. 从零开始写一个武侠冒险游戏-8-用GPU提升性能(3)

    从零开始写一个武侠冒险游戏-8-用GPU提升性能(3) ----解决因绘制雷达图导致的帧速下降问题 作者:FreeBlues 修订记录 2016.06.23 初稿完成. 2016.08.07 增加对 ...

  5. 从零开始写一个武侠冒险游戏-7-用GPU提升性能(2)

    从零开始写一个武侠冒险游戏-7-用GPU提升性能(2) ----把地图处理放在GPU上 作者:FreeBlues 修订记录 2016.06.21 初稿完成. 2016.08.06 增加对 XCode ...

  6. 从零开始写一个武侠冒险游戏-6-用GPU提升性能(1)

    从零开始写一个武侠冒险游戏-6-用GPU提升性能(1) ----把帧动画的实现放在GPU上 作者:FreeBlues 修订记录 2016.06.19 初稿完成. 2016.08.05 增加对 XCod ...

  7. 从零开始写一个Tomcat(叁)--请求解析

    挖坑挖了这么长时间也该继续填坑了,上文书讲到从零开始写一个Tomcat(贰)--建立动态服务器,讲了如何让服务器解析请求,分离servlet请求和静态资源请求,读取静态资源文件输出或是通过URLCla ...

  8. 从零开始写一个npm包及上传

    最近刚好自己需要写公有npm包及上传,虽然百度上资料都能找到,但是都是比较零零碎碎的,个人就来整理下,如何从零开始写一个npm包及上传. 该篇文件只记录一个大概的流程,一些细节没有记录. tips:  ...

  9. 从零开始写一个武侠冒险游戏-0-开发框架Codea简介

    从零开始写一个武侠冒险游戏-0-开发框架Codea简介 作者:FreeBlues 修订记录 2016.06.21 初稿完成. 2016.08.03 增加对 XCode 项目文件的说明. 概述 本游戏全 ...

随机推荐

  1. Linux中安装硬盘后对硬盘的分区以及挂载

    我将使用VM来进行模拟 先使用df看下我的电脑硬盘信息: df -h 可以看到只有一个sda1分区装载/boot,还有一个扩展分区 查看dev下的硬盘: 只有一个硬盘(两个分区) 注意: 如果你是ID ...

  2. Add correct host key in /root/.ssh/known_hosts to get rid of this message

    bug: Add correct host key in /root/.ssh/known_hosts to get rid of this message 解决办法: rm ~/.ssh/known ...

  3. 最详细的C++对应C#的数据类型转换

    C++ ---------------------- C# LDWORD ----------------IntPtr LLONG-------------------Intptr bool ---- ...

  4. c# .Net随机生成字符串代码

    /// <summary> /// 随机生成字符串 /// </summary> /// <param name="OperationType"> ...

  5. weblogic的web.xml报错----Malformed UTF-8 char -- is an XML encoding declaration missing

    weblogic报错: Malformed UTF-8 char -- is an XML encoding declaration missing 把编码修改成utf8,上传到weblogic就报这 ...

  6. java----自动类型转换

  7. Windows7安装Bitvise开启ssh服务

    Windows7安装Bitvise开启ssh服务 by:铁乐猫 在Liunx和windows10上配置SSH服务是一件很容易的事,毕竟系统己经自带了ssh的服务功能. 不过在windows7上可不容易 ...

  8. C# -- Lambda 表达式的使用

    C# -- Lambda 表达式的使用 Lambda 表达式是作为对象处理的代码块(表达式或语句块). 它可作为参数传递给方法,也可通过方法调用返回. Lambda 表达式是可以表示为委托的代码,或者 ...

  9. 【转】win2008 中iis7设置404页面但返回状态200的问题解决办法

    今天根据SEO反馈,某个站点中设置的404页面返回的http状态为200.通过站长工具进行查询,发现返回的状态确实为200. 通过彻查问题,发现这个网站的服务器环境为windows2008 服务器为i ...

  10. hTML 如何在不同页面上传递参数( 1 )

    (1).一种是重定向跳转,超连<a>就是一种重定向跳转,这样的跳转request对象是传不到下一个页面的,下一个页面得到的request对象是一个新的对象,而不是上一个页面传过来的就得不到 ...