手写promise源码大全(小白级教程,简单易懂)

2,707 阅读11分钟

image.png

前言

promise相信大家都会使用,已经存在很多年了,很多项目都存在promise的身影,在解决异步问题上promise功不可没,所以今天就来探索一下promise的源码,感受一下他的魅力

从本篇文章我们能学到什么?

  1. 学习ts的使用
  2. 学习promise的常用方法有哪些,以及用法有哪些
  3. 异步的思路是什么?异步就是先把函数先保存起来,等触发的时候,在执行
  4. 学习promise.all,promise.allSettle,promise.race, promise.any等的使用场景
  5. 学习透传

手写promise总结

  1. 先写出如何使用
  2. 再写出使用特点
  3. 基本方法返回的都是promise

为什么有promise?

promise诞生前都有哪些异步的方式?

  1. callback回调函数
    缺点:回调地狱

  2. 发布/订阅模式 缺点:需要定义全局对象,事件名称很多会冲突

所以我们要理解promise就是解决上面的缺点,但是他没有什么神奇的地方,他会吸取callback和发布/订阅模式的优点, 他成了回调函数和观察者模式的结合体

我们都要实现哪些功能

  1. 同步
  2. 异步
  3. 链式调用
  4. 透传

promise解决了什么问题?

1)回调地狱,可读性变好

2)promise可以支持多个并发的请求,获取并发请求中的数据

3)可以处理异常

4)使用发布/订阅模式处理异步问题,全局变量难以维护,变量名冲突的问题

promise源码

promise初始化

const promise = new Promise((resolve, reject) => {
  resolve("111");
});
promise.then(
  (result) => {
    console.log(result);
  },
  (reason) => {
    console.log("原因", reason);
  }
);

很简单的一个例子,输出111

promise规范

要想写出promise,先来了解4条promise规范,很简单的规范

    1. promise是一个拥有then方法的对象或者函数
    • then方法相当于观察者模式中的订阅,所以这就是为什么then需要传入函数
    1. promise有三种状态,只能从等待状态到成功或者失败()
    • 为什么要有三种状态?防止多次调用,因为你如果调用两次resolve,那么只能以第一次的为准,第一次resolve后,状态直接就变为SUCCESS,那么也就不会在执行后面的reslove
    1. then方法返回一个新的promise对象
    • 为什么要返回promise,为了支持链式调用
    1. promise初始化传入一个函数,函数上有两个参数,resolvereject,他们也都是函数
    • promise中的resolve,reject也就相当于观察者模式中的notify

首先我们初始化一下promise,三种状态,有一个then方法的类

const PENDING = "PENDING";
const SUCCESS = "SUCCESS";
const FAIL = "FAIL";

class MyPromise{
   construct(){}
   
   then(){}
}

接着我们完善construct,接受一个函数,函数上有两个函数resolve,reject,resolve接受result,reject接受reason,还要定义onSuccessCb,onFailCb,就相当于观察者模式中存储回调的数组,其实promise中回调都只有一个

constructor(execute) {
    // 初始status
    this.status = PENDING;
    this.value = "";
    this.reason = "";
    // 为什么我们要使用箭头函数,因为箭头函数中的this指向MyPromise
    const resolve = (result) => {
      // 从PENDING变为SUCCESS状态
      if (this.status === PENDING) {
        this.status = SUCCESS;
        this.value = result;
        this.onSuccessCb.forEach((fn) => fn());
      }
    };
    const reject = (reason) => {
    // 从PENDING变为FAIL状态
      if (this.status === PENDING) {
        this.status = FAIL;
        this.reason = reason;
      }
    };
    // 执行excute方法,然后执行回调resolve或者reject
    execute(resolve, reject);
  }

是不是感觉很简单

这不就是用了回调函数的原理么,没有其他的魔法

上面的内容我们很容易就写出来了,接着写then方法,返回一个promise,then方法有两个函数,onSuccess,onFail

then(onSuccess, onFail) {
    const pr = new MyPromise((resolve, reject) => {
      if (this.status === SUCCESS) {
        onSuccess(this.value);
      }
      if (this.status === FAIL) {
        onFail(this.reason);
      }
    });
    return pr;
  }

测试一下,把promise换成我们的MyPromise

const promise = new MyPromise((resolve, reject) => {
  resolve("111");
});
promise.then(
  (result) => {
    console.log(result);
  },
  (reason) => {
    console.log("原因", reason);
  }
);

也可以输出111了,棒棒哒,但是我们现在这个例子并没有实现异步功能,于是我们改写一下测试的例子,加入setTimeout

then链式调用,透传,值为promise

const promise = new MyPromise((resolve, reject) => {
  setTimeout(()=>{
    resolve("111");
  },2000);
});

我们可以想一下,如果加入异步代码,那么promise.then的时候promise是什么状态?肯定是PENDING状态,那么我们这时候需要在then方法中加入status为PENDING的情况,then方法有两个参数onSuccess,onFail,那么我们直接执行onSuccess方法肯定是不对的,因为异步后的result还没有返回,所以我们应该把方法先保存下来,在resolve方法执行的时候在执行

class MyPromise {
  constructor(execute) {
    this.status = PENDING;
    this.value = "";
    this.reason = "";
    this.onSuccessCb = [];
    const resolve = (result) => {
      if (this.status === PENDING) {
        this.status = SUCCESS;
        this.value = result;
        // resolve的时候执行
        this.onSuccessCb.forEach((fn) => fn());
      }
    };
    const reject = (reason) => {
      if (this.status === PENDING) {
        this.status = FAIL;
        this.reason = reason;
      }
    };
    execute(resolve, reject);
  }

  then(onSuccess, onFail) {
    const pr = new MyPromise((resolve, reject) => {
        // 这是同步的时候执行的
      if (this.status === SUCCESS) {
        let x = onSuccess(this.value);
        resolve(x);
      }
      if (this.status === FAIL) {
        let x = onFail(this.reason);
        reject(x);
      }
      // 异步的时候会是PEINDG,状态还没有改变的时候
      if (this.status === PENDING) {
         // 保存函数,但并不执行, 一定要考虑回调函数什么时候执行
        this.onSuccessCb.push(() => {
          let x = onSuccess(this.value);
          resolve(x);
        });
        this.onFailCb.push(()=>{
          let x = onFail(this.reason);
          reject(x);
        })
      }
    });
    return pr;
  }
}

如上面这般,我们实现了then的链式调用和异步方法,新的需求又来了 then方法的第一个参数为null,该怎么办

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject("报错了");
  }, 2000);
});
const pr = promise.then(
  (result) => {
    return result;
  }
);
pr.then(null,(error) => {
  console.log(error);
});

那么我们就要实现值穿透了,当then中传入的不是函数,则这个promise返回上一个promise的值

then(onSuccess, onFail) {
    // 实现值穿透 当then中传入的不是函数,则这个promise返回上一个promise的值
    // 相当于你在then中写了一个(value)=>{return value}
    onSuccess = typeof onSuccess==='function'?onSuccess:value => value;
    onFail = typeof onFail ==='function'?onFail: reason => reason;
    // 用于实现链式调用
    const pr = new MyPromise((resolve, reject) => {
      let success = ()=>{
        try{
          const result = onSuccess(this.value);
          resolve(result);
        }catch(err){
          reject(err);
        }
      } 

      let fail = ()=>{
        try{
          const reason = onFail(this.reason);
          reject(result);
        }catch(err){
          reject(err);
        }
      }

      switch(this.status){
        case "PENDING":
          this.onSuccessCb.push(success);
          this.onFailCb.push(fail);
          break;
        case "SUCCESS":
          success();
          break;
        case "FAIL":
          fail();
          break;    
      }
    });
    return pr;
  }

下一个需求,如果then中的第一个函数或者第二个函数返回promise该如何,就是如何实现调用完一个请求后,如何在下一个then中调用另一个请求

then(onSuccess, onFail) {
    // 实现值穿透 当then中传入的不是函数,则这个promise返回上一个promise的值
    onSuccess = typeof onSuccess==='function'?onSuccess:value => value;
    onFail = typeof onFail ==='function'?onFail: reason => reason;
    // 用于实现链式调用
    // 使用策略模式
    const pr = new MyPromise((resolve, reject) => {
      let success = ()=>{
        try{
          const result = onSuccess(this.value);
          return result instanceof MyPromise?result.then((value)=>resolve(value),(reason)=>reject(reason)):resolve(result);
        }catch(err){
          reject(err);
        }
      } 

      let fail = ()=>{
        try{
          const reason = onFail(this.reason);
          return reason instanceof MyPromise?reason.then(resolve,reject):reject(result);
        }catch(err){
          reject(err);
        }
      }

      switch(this.status){
        case "PENDING":
          this.onSuccessCb.push(success);
          this.onFailCb.push(fail);
          break;
        case "SUCCESS":
          success();
          break;
        case "FAIL":
          fail();
          break;    
      }
    });
    return pr;
  }

这样我们的then方法基本就完备了

catch方法

catch方法用于捕获异常,而then方法的第二个函数也用于捕获异常,那么是不是把catchonRejected放到then的第二个函数就行了,这是第一点,第二点,catch方法返回一个promise,所以只要return this.then(null,onRejected)就可以了,good

const p = new Promise((resolve)=>{
  resolve(1);
})
p.then(()=>{
  throw new Error('5');
}).catch(error=>{
  console.log(error,'result');
})
catch(onRejected){
    return this.then(null,onRejected);
  }

Promise.resolve() 方法

resolve相当于啥?相当于观察者模式中的通知呀,一般会直接传入一个值,凡是有例外,如果单独使用resolve方法,那么传入的还有可能是promise

传入的参数的类型

  • 基本类型
  • promise类型

因为resolve后面跟的方法是then,所以你传入promise那就可以直接返回了,你传入基本类型还需要用promise包裹一下

const p = new Promise((resolve)=>{
  resolve(1);
})
let c=p.then(()=>{
  throw new Error('5');
})

Promise.resolve(c).then(()=>{
  console.log('result');
},()=>{
  console.log("reject");
})
// 输出:reject
 static resolve(value){
    if(value instanceof MyPromise){
      return value;
    }else{
      // 返回一个promise对象
      // 用resolve包裹,改变状态
      return new MyPromise((resolve,reject)=>resolve(value));
    }
  }

Promise.reject()方法

常见resolve,reject一般都是放值类型的数据,其实也可以放promise数据

需要注意的是:Promise.resolve()不一定会改变状态(如果传入的是一个promise,那么不会改变状态),但是Promise.reject()一定会改变状态

const p = new Promise((resolve)=>{
  resolve(1);
})
let c=p.then(()=>{
  return 7;
})

Promise.reject(c).then(()=>{
  console.log('result');
},(result)=>{
  console.log("reject",result);
})

// 输出 reject Promise { 7 }
  static reject(reason){
    // Promise.reject方法的参数会原封不动地作为reject的参数
    return new MyPromise((resolve, reject) => reject(reason))
  }

Promise.all()方法

我们先来介绍一下all方法

通俗来说,.all()的作用是接收一组异步任务,然后并行执行异步任务,并且在所有异步操作执行完后才执行回调。

  1. Promise.all()方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例
  2. 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数
  3. 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数

应用场景:

1, 多个promise并发请求

缺点:如果其中一个reject,都会崩掉

function runAsync (x) { 
  const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000));
  return p;
};
Promise.all([runAsync(1), runAsync(2), runAsync(3)]).then(res => console.log(res))

 static all(promiseArr) {
    const length = promiseArr.length;
    const values = new Array(length);
    let count = 0;
    return new MyPromise((resolve, reject) => {
        for (let i = 0; i < length; i++){
            // 要考虑传入的值不是promise的情况
            // 为什么使用Promise.resolve(),因为如果是promise的直接return,如果不是的话,会套上一层
            MyPromise.resolve(promiseArr[i]).then((value) => {
                values[i] = value;
                count++;
                // 利用resolve和reject只能执行一次的特性
                // 为什么放到then里面,因为then是异步的
                if(count===length){
                    resolve(values)}
                }).catch((error) => {
                    reject(error);
            })
        }
    })
}

Promise.allSettled()方法

应用场景:通all,因为我们不希望有一个promise崩掉,全部崩掉,allSettled会返回所有的结果

Promise.all()不同,Promise.allSettled()即使是遇到reject也会等待所有的promise到最后。所以我们只需要用一个array记录各个promisesuccess或者fail结果即可。

function runAsync (x) { 
  const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000));
  return p;
};
Promise.allSettled([runAsync(1), runAsync(2), runAsync(3)]).then(res => console.log(res))
// [
//   { status: 'fulfilled', value: 1 },
//   { status: 'fulfilled', value: 2 },
//   { status: 'fulfilled', value: 3 }
// ]
static allSettled(promises) {
    let length = promises.length;
    let count = 0;
    let result = new Array(length);
    return new Promise((resolve) => {
        for (let i = 0; i < length; i++){
            promises[i].then((value) => {
                result[i] = {
                    status: SUCCESS,
                    value,
                }
                count++;
                // 为什么放到then里面,因为promise是异步的
                if (count === length) {
                    resolve(result);
                }
            }, (error) => {
                result[i] = {
                    status: FAIL,
                    reason: error
                }
                count++;
                // 为什么放到then里面,因为promise是异步的
                if (count === length) {
                    resolve(result);
                }
            })
        }
    })
}

Promise.race() 方法

应用场景,比如我们要从多个服务器获取相同的功能,那么就可以使用race

缺点: 如果有一个崩掉,就会返回崩掉的结果

.race()的作用也是接收一组异步任务,然后并行执行异步任务,只保留取第一个执行完成的异步操作的结果,其他的方法仍在执行,不过执行结果会被抛弃。

// Promise.race()是一组集合中最先解决或最先拒绝的Promise,返回一个新的Promise。其他的异步方法继续执行

function runAsync (x) { 
  const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000));
  return p;
};
Promise.race([runAsync(1), runAsync(2), runAsync(3)]).then(res => console.log(res))
return new Promise((resolve,reject)=>{
      promiseArr.forEach(item=>{
        MyPromise.resolve(item).then(
        // 后面的在给resolve值,resolve已经不接受了,因为status已经改变了,status只有在PENDING的时候才接受值
          val =>resolve(val),
          err => reject(err)
        )
      })
    })

Promise.any() 方法

应用场景,通race,但是会返回第一个成功的,如果全部失败,才会失败

MyPromise.any = function(promises){
  return new Promise((resolve,reject)=>{
    
    promises = Array.isArray(promises) ? promises : []
    let len = promises.length
    // 用于收集所有 reject 
    let errs = []
    // 如果传入的是一个空数组,那么就直接返回 AggregateError
    if(len === 0) return reject(new AggregateError('All promises were rejected'))
    promises.forEach((promise)=>{
      promise.then(value=>{
        resolve(value)
      },err=>{
        len--
        errs.push(err)
        if(len === 0){
          reject(new AggregateError(errs))
        }
      })
    })
  })
}

finally方法

new Promise((resolve, reject) => {
  setTimeout(() => resolve("result"), 2000)
})
  .finally(() => console.log("Promise ready"))
  .then(result => console.log(result))

// Promise ready
// result

注意:

  • finally 没有参数
  • finally 会将结果和 error 传递
finally(cb) {
    return this.then((value) => {
        cb();
        return value;
    }, (err) => {
        cb();
        return err;
    })
}

返回的新promise的结果状态由什么来决定

const p = new Promise((resolve, reject) => reject('出错了'))

p.then(null, function (s) {
  return s;
}).then((s) => {
  console.log(s);
}, (s) => {
  console.log('error',s);
});

输出 出错了 并没有输出console.log('error',s);的值

因为
详细表达:

    1. 如果抛出异常:新的promise变为rejected,reason为抛出的异常
    1. 如果返回的是非promise的任意值,新的promise变为resolved,value为返回的值
    1. 如果返回的是另一个新的promise,此promise的结果就成为新promise的结果

总结

基本上常用的promise方法源码都已经写了一遍,大家一定要动手实现一下,其实并没有那么难,要多写两边,会有不同的体会

参考文章