带你手写Promise,保姆级教程!!!

promise是es6处理异步的一种方案,他可以接受一个异步程序,并且获取到异步程序执行的结果。

首先熟悉Promise的功能与特性,然后根据这些去反推Promise的手写,该文章会手把手带你实现以下过程。

  • promise的状态与改变状态的方法
  • then方法
  • all方法
  • race方法

1.状态

  • pendding初始状态,fulfiled成功状态,rejected失败状态
  • 状态只有两种变化:pending->fulfiled,pending->rejected,且一旦状态改变就会固定,不会再发生改变
  • 执行resolve函数可以使Promise的实例状态由pendding->fulfiled;执行reject函数可以使Promise的实例状态由pending->rejected;
js 复制代码
//实际使用
 new Promise((resolve,reject)=>{
      resolve("成功")
    })
 new Promise((resolve,reject)=>{
      reject("失败")
    })

我们可以观察到创建Promise实例的时候,传入了一个实参executor执行器函数(resolve,reject)=>{ resolve(123) },这个executor执行器函数有两个形参resolve,reject。 resolve函数接收一个参数value reject函数接受一个参数resaon 再根据状态的特性,我们可以反推出代码:

js 复制代码
//手写
const pending = "PENDING";
const fulfiled = "FULFILED";
const rejected = "REJECTED";
class Promise {
  constructor(executor) {
    this.status = pending;
    this.value = undefined;
    this.reason = undefined;

    const resolve = (value) => {
      if (this.status !== pending) return;
      this.status = fulfiled;
      this.value = value;
    };

    const reject = (reason) => {
      if (this.status !== pending) return;
      this.status = rejected;
      this.reason = reason;
    };

    executor(resolve, reject);
  }
}

以下是执行结果: 我们可以看到Promise实例的状态,值都发生了改变,并且验证了Promise实例状态只能改变一次的特性。

2.then方法

介绍:

  • then方法接受两个参数,onFulfiledCallback函数与onRejectedCallback函数
  • 当Promise实例状态变为fulfiled时,执行回调onFulfiledCallback,并将value作为该回调的参数;
  • 当Promise实例状态变为rejected时,执行回调onRejectedCallback,并将reason作为该回调的参数 我们先上实际使用then方法代码,以便我们观察特性
js 复制代码
//实际使用
 const p1 = new Promise((resolve, reject) => {
        resolve("成功");
      }).then(
        (value) => {
          console.log("////p1", value);
        },
        (reason) => {
          console.log("////p1", reason);
        }
      );

 const p2 = new Promise((resolve, reject) => {
        reject("失败");
      }).then(
        (value) => {
          console.log("////p2", value);
        },
        (reason) => {
          console.log("////p2", reason);
        }
      );

以上代码执行结果:

该结果与我们了解到的then方法特性吻合,接下来我们来手写Promise的then方法

js 复制代码
const pending = "PENDING";
const fulfiled = "FULFILED";
const rejected = "REJECTED";
class Promise {
  constructor(executor) {
    this.status = pending;
    this.value = undefined;
    this.reason = undefined;

    const resolve = (value) => {
      if (this.status !== pending) return;
      this.status = fulfiled;
      this.value = value;
    };

    const reject = (reason) => {
      if (this.status !== pending) return;
      this.status = rejected;
      this.reason = reason;
    };

    executor(resolve, reject);
  }

  then(onFulfiledCallback, onRejectedCallback) {
    if (this.status === fulfiled) {
      onFulfiledCallback(this.value);
    }

    if (this.status === rejected) {
      onRejectedCallback(this.reason);
    }
  }
}

我们观察代码结合我们的实际使用,会发现一些问题,目前我们考虑的情况都是同步的,如果我们执行的是异步代码呢,then方法还会按照预期执行吗?我们来看看

js 复制代码
 const p1 = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve("成功");
        });
      }).then((value) => {
        console.log("////p1", value);
      });
      console.log("////p1", p1);

上面的代码会输出什么呢,看看控制台

显然不是我们的预期,我们来分析下:

首先,value为什么没有输出?因为then中的回调没有执行,那为什么Promises实例也变成undefined呢?

那是因为在我们自己的代码执行到then时,then方法中我们没有写返回值,所以就么默认返回undefined啦, 有的同学在这里立马察觉到一丝猫腻--->什么?p1的值不应该是Promise实例吗?这个先不提,我们后面再谈。

现在我们对于异步的情况对我们的then函数做一些改进,先分析现状:

如果resolve异步执行的话,我们在then方法中看到的status肯定就是pending状态,那么我们可以在pending状态时,把then的回调先存起来,等到resolve/reject执行的时候再去执行。好,确定了思路,继续搞起来

js 复制代码
const pending = "PENDING";
const fulfiled = "FULFILED";
const rejected = "REJECTED";
class Promise {
  constructor(executor) {
    this.status = pending;
    this.value = undefined;
    this.reason = undefined;
    this.onFulfiledCallbackList=[]
    this.onRejectedCallbackList=[]

    const resolve = (value) => {
      if (this.status !== pending) return;
      this.status = fulfiled;
      this.value = value;
      this.onFulfiledCallbackList.forEach(fn=>fn())
    };

    const reject = (reason) => {
      if (this.status !== pending) return;
      this.status = rejected;
      this.reason = reason;
      this.onRejectedCallbackList.forEach(fn=>fn())
    };

    executor(resolve, reject);
  }

  then(onFulfiledCallback, onRejectedCallback) {
    if (this.status === fulfiled) {
      onFulfiledCallback(this.value);
    }

    if (this.status === rejected) {
      onRejectedCallback(this.reason);
    }

    if(this.status===pending){
      this.onFulfiledCallbackList.push(()=>{
        onFulfiledCallback(this.value)
      })
      this.onFulfiledCallbackList.push(()=>{
        onRejectedCallback(this.reason)
      })
    }
  }
}

这里有些同学会有个问题,为什么要把onFulfiledCallback,onRejectedCallback存到数组里? 我们来看以下代码:

js 复制代码
  const p1 = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve("成功");
        });
      })

      p1.then(value=>{
        console.log("///我是第一");
      })

      p1.then(value=>{
        console.log("///我是第二");
      })

如果我们将onFulfiledCallback,onRejectedCallback简单的复制,那么多次调用then就会覆盖这些回调,所以要存入数组中去挨个执行。

这时候then方法就有雏形了,我们来继续

上面提到一个问题,就是p1的输出是undefind,而不是我们预期的Promise实例,先回顾下代码:

js 复制代码
 const p1 = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve("成功");
        });
      }).then((value) => {
        return 123
      });
      console.log("////p1", p1);

我们来盘一下then方法的返回值 来大声背一下八股文:

  • then方法的返回值由其回调函数的返回值确定,
  • 如果回调函数的返回值是一个非Promise实例,则返回一个成功的promise,值为回调函数的返回值。
  • 如果回调函数的返回值是一个promise,则then方法返回一个promise,该promise与回调函数返的promise同状态同值。
  • 如果回调执行报错,返回一个失败状态的promise,值为error。

好,背完了,记住以后我们来写代码:

js 复制代码
const pending = "PENDING";
const fulfiled = "FULFILED";
const rejected = "REJECTED";
class Promise {
  constructor(executor) {
    this.status = pending;
    this.value = undefined;
    this.reason = undefined;
    this.onFulfiledCallbackList = [];
    this.onRejectedCallbackList = [];

    const resolve = (value) => {
      if (this.status !== pending) return;
      this.status = fulfiled;
      this.value = value;
      this.onFulfiledCallbackList.forEach((fn) => fn());
    };

    const reject = (reason) => {
      if (this.status !== pending) return;
      this.status = rejected;
      this.reason = reason;
      this.onRejectedCallbackList.forEach((fn) => fn());
    };

    executor(resolve, reject);
  }

  then(onFulfiledCallback, onRejectedCallback) {
    return new Promise((resolve, reject) => {

      const handle=(cb)=>{
        try {
          const x = cb(this.value);
          if (x instanceof Promise) {
            x.then(
              (value) => {
                resolve(value);
              },
              (reason) => {
                reject(reason);
              }
            );
          } else {
            resolve(x);
          }
        } catch (error) {
          reject(error)
        }
      }
      
      
      if (this.status === fulfiled) {
        handle(onFulfiledCallback)
      }

      if (this.status === rejected) {
        handle(onRejectedCallback)
      }

      if (this.status === pending) {
        this.onFulfiledCallbackList.push(() => {
          handle(onFulfiledCallback)
        });
        this.onFulfiledCallbackList.push(() => {
          handle(onRejectedCallback)
        });
      }
    });
  }
}

来再看执行结果

因为then方法返回的是promise实例,那说明也可以链式调用了,我们链式调用试试:

js 复制代码
const p1 = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve("成功");
        }, 1000);
      })
        .then((value) => {
          return 123;
        })
        .then((value) => {
          console.log(value, "链式调用成功");
        });

执行结果,成功!

3.all方法

先来温习下promise的all方法的特性

  • all方法通过Promise构造函数直接调用,这就说明all方法是构造函数的静态方法,不是显式原型里的方法
  • all方法也返回一个promise实例。
  • all方法接收一个promise实例数组,数组中的promise全部执行成功才会变为fulfiled状态,只要有一个失败就是rejected状态
  • fulfiled的值是由传入promise数组成功执行的值组成的数组,且顺序一致,对于非promise实例的成员,值为该成员本身。
  • 失败的值是第一个失败的promise成员失败的值

我们根据以上特性来写all方法:

js 复制代码
  static all(pList) {
    return new Promise((resolve, reject) => {
      const result = [];
      let i = 0;

      const addResult = (v,index) => {
        result[index]=v;
        i++;
        if (i === pList.length) {
          resolve(result);
        }
      };
      pList.forEach((p,index) => {
        if (p instanceof Promise) {
          p.then(
            (v) => {
              addResult(v,index);
            },
            (r) => {
              reject(r);
            }
          );
        } else {
          addResult(p,index);
        }
      });
    });
  }

4.race方法

race方法也是promise的静态方法,接受一个数组,返回一个promise实例,这个实例的状态就是数组中第一个执行完成的promise对象的状态,值也是。 让我们来实现:

js 复制代码
  static race(pList) {
    return new Promise((resolve, reject) => {
      pList.forEach((p) => {
        if (p instanceof Promise) {
          p.then((v) => {
            resolve(v);
          }),
            (r) => {
              reject(r);
            };
        } else {
          resolve(p);
        }
      });
    });
  }
相关推荐
J老熊6 小时前
JavaFX:简介、使用场景、常见问题及对比其他框架分析
java·开发语言·后端·面试·系统架构·软件工程
猿java6 小时前
什么是 Hystrix?它的工作原理是什么?
java·微服务·面试
陪学8 小时前
百度遭初创企业指控抄袭,维权还是碰瓷?
人工智能·百度·面试·职场和发展·产品运营
大数据编程之光10 小时前
Flink Standalone集群模式安装部署全攻略
java·大数据·开发语言·面试·flink
ifanatic11 小时前
[面试]-golang基础面试题总结
面试·职场和发展·golang
程序猿进阶12 小时前
堆外内存泄露排查经历
java·jvm·后端·面试·性能优化·oom·内存泄露
长风清留扬14 小时前
一篇文章了解何为 “大数据治理“ 理论与实践
大数据·数据库·面试·数据治理
周三有雨1 天前
【面试题系列Vue07】Vuex是什么?使用Vuex的好处有哪些?
前端·vue.js·面试·typescript
爱米的前端小笔记1 天前
前端八股自学笔记分享—页面布局(二)
前端·笔记·学习·面试·求职招聘
好学近乎知o1 天前
解决sql字符串
面试