ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

手撕promise源码及其原理

2021-11-11 16:32:01  阅读:234  来源: 互联网

标签:status resolve 源码 let Promise promise reject 原理


前言

哈哈,好久没写博客了,主要是因为这些日子刚入职了网易,一直在适应工作,还有学校里各种乱七八糟的琐事,所以一直没有来得及写。今天刚好做完了一个迭代,难得有空闲时间来写篇博客(其实是摸鱼~)。
之前我也写过一篇手撕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+规范:
在这里插入图片描述

  • 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和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;

附上一个通过测试的截图
在这里插入图片描述

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;

标签:status,resolve,源码,let,Promise,promise,reject,原理
来源: https://blog.csdn.net/qq_44014425/article/details/121204434

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有