手撕promise源码及其原理

前言

哈哈,好久没写博客了,主要是因为这些日子刚入职了网易,一直在适应工作,还有学校里各种乱七八糟的琐事,所以一直没有来得及写。今天刚好做完了一个迭代,难得有空闲时间来写篇博客(其实是摸鱼~)。
之前我也写过一篇手撕promise,只不过那篇只有代码,没有解释,所以不太容易理解。这篇我准备先从理论说起,再配合代码进行介绍。

如果在看这篇文章之前还没有了解promise的基础知识,请访问JS的Promise对象

哈哈哈哈,就不说废话了,直接进入正题。

一、手撕Promise必须知道的一些概念

1. 高阶函数

高阶函数的概念:
(1)一个函数的参数是一个函数,我们可以成这个函数为高阶函数。
(2)一个函数返回一个函数,我们可以称这个函数为高阶函数。
(3)符合以上任意一点,我们就可以称这个函数为高阶函数。

2. 装饰器模式

概念(摘自菜鸟教程):
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。

这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。

将高阶函数和装饰器模式结合使用,举个例子:

  function say(who) { // 普通的函数
    console.log("say", who);
  }
  // 对原函数进行扩展 但是不破坏原函数
  // @装饰器对类来扩展
  Function.prototype.before = function (beforeSay) {
    // 接受到了回调方法
    return (...args) => {
      // newSay
      beforeSay(...args);
      this(...args);
    };
  };
  let beforeSay = (args) => {
    // 传入一个回调方法
    console.log("say before", args);
  };
   let newSay = say.before(beforeSay);
  newSay("我"); // 这里调用的应该是新的方法

这里使用装饰器模式给原来的say方法加了一个新的功能beforeSay

3. 柯里化函数

我之前写过一篇柯里化函数的博客,简单来说,柯里化函数就是利用了闭包的预存储的功能,它将一个多个参数的函数,转化成一个个参数传入的函数。
举个例子:

// 原本fn有四个参数,a,b,c,d
function fn(a,b,c,d){...}
// 通过柯里化,可以转化成 fn(a)(b)(c)(d)

4. 发布订阅模式

发布订阅模式是前端中一种常见的设计模式vue中大量使用的观察者模式便是基于发布订阅模式衍变而来的。
发布订阅模式有一个发布者、一个订阅者和一个事件池。发布者将消息发布到事件池中,订阅者可以从事件池中订阅消息,事件池有一个on方法和一个emit方法,通过on方法发布消息,通过emit方法执行消息事件。在发布订阅模式中,发布者和订阅者没有强耦合关系。
举个例子(文件在node环境中执行):

const fs = require("fs");
let events = {
	arr = [],
	on(){
		this.arr.push(fn);
	}
	emit(){
		this.arr.forEach(fn=>fn());
	}
}
events.on(function(){
    console.log('每次读取完毕后都执行')
})
events.on(function(){
    if(Object.keys(person).length === 2){
        console.log('读取完毕')
    }
})
let person= {};
fs.readFile("./a.txt", "utf8", function (err, data) {
  person.name = data;
  events.emit();
});
fs.readFile("./b.txt", "utf8", function (err, data) {
  person.age = data;
  events.emit();
});

5. 观察者模式

观察者模式中有一个观察者和一个被观察者,被观察者有一个自身状态,当自身状态改变了,通知所有观察者执行update方法触发事件。在观察者模式中,观察者和被观察者是强耦合的。
举个例子:

class Subject { // 被观察者 (需要有一个自身的状态 ,状态变化了要通知所有的观察者)
    constructor(name){
        this.name = name
        this.observers = []
        this.state = '我开心的在玩'
    }
    attach(o){
        this.observers.push(o);
    }
    setState(newState){
        this.state = newState;
        this.observers.forEach(o=>o.update(this));
    }
}
class Observer{ // 观察者
    constructor(name){
        this.name = name;
    }
    update(s){
        console.log(this.name+":" + s.name +'当前的状态是'+s.state)
    }
}
let s = new Subject('小宝宝')
let o1 = new Observer('爸爸');
let o2 = new Observer('妈妈');

// 订阅模式
s.attach(o1)
s.attach(o2)
s.setState('有人咬我,不开心')
s.setState('老师表扬我了 开心了')

6. 手写promise基础版

有了上面的基础,我们进行手写promise就会容易些了
首先根据promise的定义:

  1. promise是一个类,在使用的时候需要new这个类
  2. 在newPromise的时候,需要传入一个executor执行器,默认会立即被调用,而且参数有两个:resolve和reject
  3. promise有三个状态,分别是:pendding 默认等待态 onfulfilled 成功态 onrejected 失败态
    我们的promise默认就是pendding,当用户调用resolve时会变成成功态,调用reject的时候会变成失败态
    成功可以传入成功的原因,失败可以传入失败的原因
  4. new Promise 会返回一个promise实例 这个实例上有一个then方法 , then方法中有两个参数一个是成功的回调,一个是失败的回调
  5. 走向失败有两种情况:reject() 、用户主动抛出异常
  6. 一个promise中可以then多次 (发布订阅模式)
  7. promise的状态是不能从成功变成失败,也不能从失败变成成功 只有pending的时候才能更改状态

我们来写一个低配版promise

const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";
class Promise {
  constructor(exector) {
    this.status = PENDING;
    this.value = undefined; // 成功的原因
    this.reason = undefined; // 失败的原因

    this.onResolvedCallbacks = []; // 存放成功的回调
    this.onRejectedCallbacks = []; // 存放失败的回调
    const resolve = (value) => {
      if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;
        this.onResolvedCallbacks.forEach((fn) => fn());
      }
    }; // 每次new 都生成两个方法 reoslve,reject
    const reject = (reason) => {
      if (this.status === PENDING) {
        this.status = REJECTED;
        this.reason = reason;
        this.onRejectedCallbacks.forEach((fn) => fn());
      }
    };
    try {
      exector(resolve, reject); // 传递给用户两个参数
    } catch (e) {
      reject(e);
    }
  }
  then(onFulfilled, onRejected) {
    if (this.status == FULFILLED) {
      onFulfilled(this.value);
    }
    if (this.status == REJECTED) {
      onRejected(this.reason);
    }
    if (this.status == PENDING) {
      // 稍后成功了 除了执行回调外 还有其他的逻辑
      this.onResolvedCallbacks.push(() => {
        // todo...
        onFulfilled(this.value);
      });
      this.onRejectedCallbacks.push(() => {
        // todo...
        onRejected(this.reason);
      });
    }
  }
}

module.exports = Promise;

7. 手写promise完全版

接下来让我们完善一下我们写的promise,根据promiseA+规范:
手撕promise源码及其原理

  • then方法必须返回一个promise
  • 如果onFulfilled或onRejected返回一个x,就执行 Promise Resolution procedure 并把x传进去
  • 如果onFulfilled不是一个函数并且我们的promise实例已经为满足态,则then返回的promise实例必须为满足态并且和我们的promise实例有相同的值
  • 如果onRejected不是一个函数并且我们的promise实例已经为失败态,则then返回的promise实例必须为失败态并且和我们的promise实例有相同的失败原因
  • 并且为了执行 Promise Resolution procedure ,我们要为其增加一个promise规范中最核心的一个方法:resolvePromise。
    上代码:
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

function resolvePromise(x, promise2, resolve, reject) {
  // x 决定promise2的状态走成功还是失败

  // 所有promise都要遵循这个规范,这样就可以保证不同人写的promise可以混用
  // 核心就在这个resolvePromise方法中
}

class Promise {
  constructor(executor) {
    this.status = PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];
    const resolve = (value) => {
      if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;
        this.onFulfilledCallbacks.forEach(fn => fn());
      }
    }
    const reject = (reason) => {
      if (this.status === PENDING) {
        this.status = REJECTED;
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn => fn());
      }
    }
    try {
      executor(resolve, reject);
    } catch (e) {
      resolve(e);
    }
  }

  then(onFulfilledCallback, onRejectedCallback) {
    let promise2 = new Promise((resolve, reject) => {
      if (this.status === FULFILLED) {
        setTimeout(()=>{
          try{
            let x = onFulfilledCallback(this.value);
            resolvePromise(x, promise2, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      }
      if (this.status === REJECTED) {
        setTimeout(()=>{
          try{
            let x = onRejectedCallback(this.reason);
            resolvePromise(x, promise2, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      }
      if (this.status === PENDING) {
        this.onFulfilledCallbacks.push(() => {
          setTimeout(()=>{
            try {
              let x = onFulfilledCallback(this.value);
              resolvePromise(x, promise2, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
        this.onRejectedCallbacks.push(() => {
          setTimeout(()=>{
            try{
              let x = onRejectedCallback(this.reason);
              resolvePromise(x, promise2, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
        return promise2;
      }
    });
  }
}

module.exports = Promise;

上述内容完成后,我们自己的promise已经初具雏形了,接下来就让我们来写promise中最核心的一个方法:resolvePromise方法。同样是根据Promise/A+规范:
在这里插入图片描述
手撕promise源码及其原理

  • 如果promise和x引用了同一个对象,则返回一个拒绝态的promise,结果为一个TypeError
  • 如果x是一个promise
    • 如果x状态为pending,promise必须保持pending状态知道x变成fulfilled态或者rejected态
    • 当x被满足时(就是变成满足态时),以相同的值来执行promise
    • 当x被拒绝时,以相同的原因来执行promise
  • 除此之外,如果x是一个对象或者一个函数(这个地方用来判断x是否为一个promise,是用来规范不同的人写的promise的,并且这条规则可以覆盖上三条规则)
    • 定义一个then变量,把x.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变成满足态
  • 如果x不是一个对象或函数,让x变成满足态
    上代码:
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

function resolvePromise(x, promise2, resolve, reject) {
  // x 决定promise2的状态走成功还是失败
  if (promise2 === x) {
    return reject(new TypeError("循环引用"));
  }
  // 判断x是不是一个promise先保证x得是一个对象或者函数,如果不是对象或者函数,则x一定不是promise
  if ((typeof x === "object" && x !== null) || typeof x === "function") {
    let called; // 我们用called判断下面过程是否执行过了,如果执行过了,就不再执行
    // 我们需要看这个x上有没有then方法,有then方法才说明它是一个promise
    try {
      let then = x.then;  //x可能是别人写的promise,那么取then有风险
      if (typeof then === "function") {
        then.call(x, y => {
          if (called) return;
          called = true;
          resolvePromise(y, promise2, resolve, reject); // 递归解析直到我们的y的值是一个普通值
        }, r => {
          if (called) return;
          called = true;
          reject(r);
        })
      } else {  // 没有then方法都执行这里
        resolve(x); // 这里x只是一个普通对象
      }
    } catch (e) {
      if (called) return;
      called = true;
      reject(e);
    }
  } else {
    // 这里x只是一个普通的值,直接把x传给promise2即可
    resolve(x);
  }

  // 所有promise都要遵循这个规范,这样就可以保证不同人写的promise可以混用
  // 核心就在这个resolvePromise方法中
}

class Promise {
  constructor(executor) {
    this.status = PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];
    const resolve = (value) => {
      if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;
        this.onFulfilledCallbacks.forEach(fn => fn());
      }
    }
    const reject = (reason) => {
      if (this.status === PENDING) {
        this.status = REJECTED;
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn => fn());
      }
    }
    try {
      executor(resolve, reject);
    } catch (e) {
      reject(e);
    }
  }

  then(onFulfilledCallback, onRejectedCallback) {
    // 有可能这个onFulfilledCallback,onRejectedCallback是可选的,所以用户没填,我们要自己给它补上(穿透特性)
    onFulfilledCallback = typeof onFulfilledCallback === "function" ? onFulfilledCallback : function (data) {
      return data;
    };
    onRejectedCallback = typeof onRejectedCallback === "function" ? onRejectedCallback : err =>{
      throw err;
    };
    let promise2 = new Promise((resolve, reject) => {
      if (this.status === FULFILLED) {
        setTimeout(() => {
          try {
            let x = onFulfilledCallback(this.value);
            resolvePromise(x, promise2, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      }
      if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            let x = onRejectedCallback(this.reason);
            resolvePromise(x, promise2, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      }
      if (this.status === PENDING) {
        this.onFulfilledCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilledCallback(this.value);
              resolvePromise(x, promise2, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onRejectedCallback(this.reason);
              resolvePromise(x, promise2, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
      }
    });
    return promise2;
  }
}

// 安装此模块来测试自己写的promise是否符合规范,并且要加上Promise.deferred
// npm install promises-aplus-tests -g
// promises-aplus-tests 3.promise
// catch Promise.resolve Promise.reject

// 在测试的时候 会测试你的promise对象是否符合规范
Promise.deferred = function () {
  let dfd = {};
  dfd.promise = new Promise((resolve, reject) => {
    dfd.resolve = resolve;
    dfd.reject = reject;
  })
  return dfd
}

module.exports = Promise;

附上一个通过测试的截图
手撕promise源码及其原理

8. 完善promise其他功能

加入catch、all、finally方法

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

function resolvePromise(x, promise2, resolve, reject) {
  // x 决定promise2的状态走成功还是失败
  if (promise2 === x) {
    return reject(new TypeError("循环引用"));
  }
  // 判断x是不是一个promise先保证x得是一个对象或者函数,如果不是对象或者函数,则x一定不是promise
  if ((typeof x === "object" && x !== null) || typeof x === "function") {
    let called; // 我们用called判断下面过程是否执行过了,如果执行过了,就不再执行
    // 我们需要看这个x上有没有then方法,有then方法才说明它是一个promise
    try {
      let then = x.then;  //x可能是别人写的promise,那么取then有风险
      if (typeof then === "function") {
        then.call(x, y => {
          if (called) return;
          called = true;
          resolvePromise(y, promise2, resolve, reject); // 递归解析直到我们的y的值是一个普通值
        }, r => {
          if (called) return;
          called = true;
          reject(r);
        })
      } else {  // 没有then方法都执行这里
        resolve(x); // 这里x只是一个普通对象
      }
    } catch (e) {
      if (called) return;
      called = true;
      reject(e);
    }
  } else {
    // 这里x只是一个普通的值,直接把x传给promise2即可
    resolve(x);
  }

  // 所有promise都要遵循这个规范,这样就可以保证不同人写的promise可以混用
  // 核心就在这个resolvePromise方法中
}

class Promise {
  constructor(executor) {
    this.status = PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];
    const resolve = (value) => {
      if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;
        this.onFulfilledCallbacks.forEach(fn => fn());
      }
    }
    const reject = (reason) => {
      if (this.status === PENDING) {
        this.status = REJECTED;
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn => fn());
      }
    }
    try {
      executor(resolve, reject);
    } catch (e) {
      reject(e);
    }
  }

  then(onFulfilledCallback, onRejectedCallback) {
    // 有可能这个onFulfilledCallback,onRejectedCallback是可选的,所以用户没填,我们要自己给它补上(穿透特性)
    onFulfilledCallback = typeof onFulfilledCallback === "function" ? onFulfilledCallback : function (data) {
      return data;
    };
    onRejectedCallback = typeof onRejectedCallback === "function" ? onRejectedCallback : err => {
      throw err;
    };
    let promise2 = new Promise((resolve, reject) => {
      if (this.status === FULFILLED) {
        setTimeout(() => {
          try {
            let x = onFulfilledCallback(this.value);
            resolvePromise(x, promise2, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      }
      if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            let x = onRejectedCallback(this.reason);
            resolvePromise(x, promise2, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      }
      if (this.status === PENDING) {
        this.onFulfilledCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilledCallback(this.value);
              resolvePromise(x, promise2, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onRejectedCallback(this.reason);
              resolvePromise(x, promise2, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
      }
    });
    return promise2;
  }

  catch(errCallback) {
    return this.then(null, errCallback);
  }

  static reject(reason) {
    return new Promise((resolve, reject) => {
      reject(reason);
    });
  }

  static resolve(value) {
    return new Promise((resolve, reject) => {
      resolve(value);
    });
  }

  static all(values) {
    return new Promise((resolve, reject) => {
      let times = 0;
      const arr = [];

      function processMap(key, value) {
        arr[key] = value;
        if (++times === values.length) {
          resolve(arr);
        }
      }

      for (let i = 0; i < values.length; i++) {
        let val = values[i];  // 可能是promise,也可能是普通值
        let then = val && val.then;
        if (typeof then === "function") {
          then.call(
            val,
            data => {
              // 获取成功的结果
              processMap(i, data);
            },
            reject
          );
        } else {
          processMap(i, val);
        }
      }
    });
  }

  static race(values) {
    return new Promise((resolve, reject) => {
      for (let i = 0; i < values.length; i++) {
        let p = values[i]; //  p可能是promise 也可能是普通值
        // 无论谁先成功就成功 谁先失败就失败
        if (p instanceof Promise) {
          p.then(resolve, reject);
        } else {
          Promise.resolve(p).then(resolve, reject);
        }
      }
    });
  };

  finally(cb) {
    return this.then(
      y => {
        return Promise.resolve(cb()).then(() => y);
      },
      r => {
        return Promise.resolve(cb()).then(() => {
          throw r;
        });
      }
    );
  }
}
module.exports = Promise;
上一篇:Git拉取小步骤


下一篇:Linux部署Nacos并配置MySQL数据源,把Springboot的配置文件放到nacos并使用