纯手写之一步步剖析promise

148 阅读6分钟

说起promise,很多人都不陌生,网络上也有很多手写原理的文章,但是以前在实际运用的时候还是发现有同时对其原理不懂,看规范都是英文,对英文不好的同学来说很费劲,所以专门写了这边文章,第一是给入门的同学提供一些学习资料和手写思路,第二是让自己回顾一下,温故而知新

1:什么是promise

Promise 是ECMAscript 6 原生提供的对象;是异步编程的一种解决方案;

2:基于promise A+规范手写(逐步分析)

1.promise是一个类 在使用的时候 需要new这个类

//所以我们首先定义一个类,不会ES6的同学去先看看class这一块知识
class Promise{

}

2,在newPromise的时候 需要传入一个executor执行器 默认会立即被调用,而且参数有两个 resolve,reject

class Promise{
    constructor(exector){
        exector(resolve,reject)
    }
}

3.promise有三个状态 分别是 pending 默认等待态 fulfilled 成功态 rejected 失败态,我们的promise默认就是pendding 当用户调用resolve时会变成成功态 调用reject的时候会变成失败态,成功可以传入成功的原因 失败可以传入失败的原因

    const PENDING = "pending";
    const FULFILLED = "fulfilled";
    const REJECTED = "rejected";
    class Promise{
        constructor(exector){
            this.status=PENDING;
            this.Result=undinfined;           
            const resolve=(Result)=>{
                this.status=FULFILLED;
                this.Result=Result;
            }
            const reject=(Result)=>{
                this.status=REJECTED;
                this.Result=Result;
            }
            exector(resolve,reject)
        }
}

4.走向失败有两种情况 reject() 用户主抛出异常,还有就是exector函数执行错误,且promise的状态是不能从成功变成失败,也不能从失败变成成功 只有pendingg的时候才能更改状态

    const PENDING = "pending";
    const FULFILLED = "fulfilled";
    const REJECTED = "rejected";
    class Promise{
        constructor(exector){
            this.status=PENDING;
            this.Result=undinfined;    
            
            const resolve=(Result)=>{
                if(this.status==PENDING){//只有状态是PENDING的时候才可以修改为其他状态
                    this.status=FULFILLED;
                    this.Result=Result;
                }
                
            }
            const reject=(Result)=>{
                if(this.status==PENDING){//只有状态是PENDING的时候才可以修改为其他状态
                    this.status=REJECTED;
                    this.Result=Result;
                }
            }
            
            try{
                exector(resolve,reject)
            }catch(e){
                reject(e);
            }
            
        }
}
//其实写到这里对promise应该就有个初步的认识了,单这里还没涉及到他的核心方法,如果这一块没弄懂的话不建议往下看了,好好补充一下基础知识,这里涉及到类,回调等知识

5.new Promise 会返回一个promise实例 这个实例上有一个then方法 , then方法中有两个参数一个是成功的回调一个是失败的回调

    const PENDING = "pending";
    const FULFILLED = "fulfilled";
    const REJECTED = "rejected";
    class Promise{
        constructor(exector){
            this.status=PENDING;
            this.Result=undinfined;    
            
            const resolve=(Result)=>{
                if(this.status==PENDING){
                    this.status=FULFILLED;
                    this.Result=Result;
                }
                
            }
            const reject=(Result)=>{
                if(this.status==PENDING){
                    this.status=REJECTED;
                    this.Result=Result;
                }
            }       
            try{
                exector(resolve,reject)
            }catch(e){
                reject(e);
            }
            
        }
        //then方法,次方法为promise的精髓所在,此事我们先实现单个的then,then链的实现放在后面几步
        then(onFulfilled, onRejected){
            //如果状态为成功,那就调用成功的方法
            if (this.status == FULFILLED) {
              onFulfilled(this.value);
            }
            //如果状态为失败,那就调用失败的方法
            if (this.status == REJECTED) {
              onRejected(this.reason);
            }
        }
}
//看到此处你已经逐步接近整个promise的原理了,而且当exector为同步代码的时候已经实现then的调用了,但此时如果你如下操作:
let pro = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject("ok");
  }, 1000);
});
你就会发现问题,你的代码达不到你想要的效果,那么异步的我们怎么处理呢?这就是promise的精髓之一,利用发布订阅原理等到状态改变的时候再去做修改。

6.exector 为异步的处理方式

    const PENDING = "pending";
    const FULFILLED = "fulfilled";
    const REJECTED = "rejected";
    class Promise{
        constructor(exector){
            this.status=PENDING;
            this.Result=undinfined;    
            //至于为什么此处是数组,因为如果你new一个promise实例P1,你P1可以多次点then执行; let p2=p1.then();  let p3=p1.then(); let p4=p1.then();  (发布订阅模式)
            this.onResolvedCallbacks = []; // 存放成功的回调
            this.onRejectedCallbacks = []; // 存放失败的回调
            const resolve=(Result)=>{
                if(this.status==PENDING){
                    this.status=FULFILLED;
                    this.Result=Result;
                    //等到成功的时候把存起来的成功的方法执行
                    this.onResolvedCallbacks.forEach((fn) => fn());
                }
                
            }
            const reject=(Result)=>{
                if(this.status==PENDING){
                    this.status=REJECTED;
                    this.Result=Result;
                    //等到失败的时候把存起来的失败的方法执行
                    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){
                //如果exector函数为异步,那么此时执行到then的时候状态还没改变,那么我么就把方法存起来,等到
                this.onResolvedCallbacks.push(onFulfilled)
                this.onRejectedCallbacks.push(onRejected)
            }
        }
}
//这一步没弄懂的前提下就别看下面的了,去补习一下发布订阅模式,和JS的同步异步

基本面试的时候能写到这里就差不多了,到时候then链式调用的原理说清楚就可以了,不过有的大公司可能要求你直接写出then 链,那么你继续往下看。

7.then 链的实现

promise 为何能实现链式的写法呢?那是因为每个then方法都返回了一个promise实例,这样promise自然可以调用原型上的方法(此处不懂的就去看看原型原型链的基本知识),废话不多说,上代码:

    const PENDING = "pending";
    const FULFILLED = "fulfilled";
    const REJECTED = "rejected";
    class Promise{
        constructor(exector){
            this.status=PENDING;
            this.Result=undinfined;
            this.onResolvedCallbacks = []; 
            this.onRejectedCallbacks = []; 
            const resolve=(Result)=>{
                if(this.status==PENDING){
                    this.status=FULFILLED;
                    this.Result=Result;
                    this.onResolvedCallbacks.forEach((fn) => fn());
                }
                
            }
            const reject=(Result)=>{
                if(this.status==PENDING){
                    this.status=REJECTED;
                    this.Result=Result;
                    this.onRejectedCallbacks.forEach((fn) => fn());
                }
            }       
            try{
                exector(resolve,reject)
            }catch(e){
                reject(e);
            }
            
        }
        then(onFulfilled, onRejected){
            let promise2=new promise((resolve, reject)=>{
                if (this.status == FULFILLED) {
                  setTimeout(() => {
                      try {
                        let x = onFulfilled(this.value);
                        resolvePromise(x, promise2, resolve, reject);//此处就是更具X的返回值去处理这个promise2的成功和失败的执行
                      } catch (e) {
                        reject(e);
                      }
                    }, 0);
                }
                if (this.status == REJECTED) {
                  setTimeout(() => {
                      try {
                        let x = onRejected(this.reason);
                        resolvePromise(x, promise2, resolve, reject);
                      } catch (e) {
                        reject(e);
                      }
                    }, 0);
                }
                if(this.status==PENDING){
                    this.onResolvedCallbacks.push(() => {
                      setTimeout(() => {
                        try {
                          let x = onFulfilled(this.value);
                          resolvePromise(x, promise2, resolve, reject);
                        } catch (e) {
                          reject(e);
                        }
                      }, 0);
                    })
                    this.onRejectedCallbacks.push(() => {
                      setTimeout(() => {
                        try {
                          let x = onRejected(this.reason);
                          resolvePromise(x, promise2, resolve, reject);
                        } catch (e) {
                          reject(e);
                        }
                      }, 0);
                    })
                }
            })
            return promise2;    
        }
}

then中的代码解释:

此时我们新增一个promise2,并把它返回,并把上一步处理的方法放倒new 的promise里,因为new promise的时候里面的代码是立即执行的;接下来的东西就有点绕了,仔细看清,这个是promise真正的精髓和重点了:

new的这个promise的是执行resolve,还是reject是取决于你onFulfilled, onRejected的执行结果,(先别纠结定时器,后面回解释),先看里面的 ,此时定义一个X 用来接收onFulfilled, onRejected 的执行结果,在执行中,如果代码直接报错,那么毫不犹豫的走新new的这个promise的reject方法,如果成功就看这个X是什么值,如果这个X是普通值,就把他当成成功,如果X是一个promise对象,则去执行X上的then方法,

为何要用定时器

因为 resolvePromise 中用到了promise2,这个promise2 是在new promise之后产生的,所以要想渠道promise2 必须使用异步,

下面就是resolvePromise 函数

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;//防止别人的promise调用两次成功或者失败
    // 我需要看 这个x 上有没有then方法 有then方法才说明他是一个promise
    try{
      let then = x.then; // x可能是别人写的promise 那么取then有风险,
      if(typeof then === 'function'){ 
        then.call(x,(y)=>{ // x.then((y)=>{},r=>{}) 取then就会有风险
          if(called) return;
          called = true;
          resolvePromise(y,promise2,resolve,reject); // 递归解析直到我们的y的值是一个普通值,因为y可能也是一个promise
        },(r)=>{
          if(called) return;
          called = true;
          reject(r);
        })
      }else{ //  没有then方法的都执行这里
        resolve(x); // 只是一个对象而已 就是一个普通值
      }
    }catch(e){
      if(called) return;
          called = true;
      reject(e);
    }
  }else{
     // x 就是一个普通的值,直接把x 传递给promise2的成功即可
     resolve(x);
  }
}