JS笔记——Promise的基本原理

196 阅读11分钟

前言

大家好,我是NobodyDJ,一名默默无闻的前端学徒。

Promise是我在学习JavaScript里,比较疑惑的知识点,一直在死记硬背它的用法,不懂其内部实现原理,不求甚解的状态,导致我日常开发中只会机械化地使用它们。因此,我整理一些Promise内部的工作原理,来帮助自己梳理其中的运作过程 ,希望也能给你一些帮助。

Promise基本用法的原理

resolve和reject

首先,es6规定,Promise对象是一个构造函数,用来生成Promise实例。以下创建了一个Promise实例:

let promise = new Promise(function(resolve, reject){
  // ...some code
})

如上,代码所示,Promise构造函数主要接收了两个参数,一个是resolve函数,一个是reject函数。我们知道当执行resolve方法时,Promise对象的状态由“未完成”转变为“成功”(pending=>resolved);当执行resolve方法时,Promise对象的状态由"未完成"转变为“失败”(pending=>rejected),并且Promise对象状态一旦发生了以上两种之一的转变,就不会再改变了(被称为状态凝固),现在来看看Promise内部是如何实现resolve()方法和reject()方法。

关键问题

从刚刚的描述中,我们可以知道有如下几个关键问题:

  1. Promise对象一开始是处于pending状态的。
  2. 调用resolve()方法后,Promise的状态由pending转变为resolved。
  3. 调用reject()方法后,Promise的状态由pending转变为rejected。
  4. Promise的状态一旦发送了改变,就不能再改变了。

实现代码

具体实现的代码如下:

class MyPromise{
  constructor(executor){
    // 初始化
    this.initValue();
    // 初始化this绑定
    this.initBind();
    // 执行Promise构造函数传进来的函数,try/catch是对throw方法错误异常的处理
    try{
      executor(this.resolve, this.reject)
    }catch(e){
      this.reject(e)
    }
  }
  // 初始化方法
  initValue(){
    this.PromiseResult = null;
    this.PromiseState = 'pending'
  }
  // 初始化this绑定的方法
  initBind(){
    // 将原型对象的方法始终绑定在实例上
    this.resolve = this.resolve.bind(this)
    this.reject = this.reject.bind(this)
  }
  // resolve方法
  resolve(value) {
      // 这里实现了Promise对象无法改变的功能
      if (this.PromiseState !== 'pending') return
      this.PromiseState = 'fulfilled';
      this.PromiseResult = value;
  }
  // reject方法
  reject(reason) {
      if (this.PromiseState !== 'pending') return
      this.PromiseState = 'rejected'
      this.PromiseResult = reason
  }
}

测试用例

测试用例分别对应了以上4个关键字

// 执行resolve()方法
const test1 = new MyPromise((resolve, reject) => {
    resolve('成功')
})
console.log(test1)
// 执行reject()方法
const test2 = new MyPromise((resolve, reject) => {
    reject('失败')
})
console.log(test2)
// 检验Promise对象的状态能否被再次改变
const test3 = new MyPromise((resolve, reject) => {
    reject('失败')
    resolve('成功')
})
console.log(test3)
// 检验Promise实例化时,抛出错误时
const test4 = new MyPromise((resolve, reject) => {
    throw ('失败')
})
console.log(test4)

测试结果如下:

then方法

Promise方法是存在于Promise原型对象Promise.prototype上的。它的作用是为Promise实例添加状态改变时的回调函数。

关键问题

主要有以下几个关键点:

  1. then方法接收两个回调函数,一个是成功的回调,另一个是失败的回调。
  2. 当执行完resolve函数,Promise状态变为resolved此时执行成功的回调;当执行完reject函数,Promise状态变为rejected状态,此时,执行失败的回调。
  3. resolve与reject存在异步操作时,必须等到执行完resolve与reject之后,才能调用then方法
  4. then方法支持链式调用,then方法会接收上一次then方法返回值的影响。

代码实现

首先,实现第一个与第二个关键点,根据Promise的状态分别执行对应的回调函数

class MyPromise{
  constructor(executor){
    // 初始化
    this.initValue();
    // 初始化this绑定
    this.initBind();
    // 执行Promise构造函数传进来的函数,try/catch是对throw方法错误异常的处理
    try{
      executor(this.resolve, this.reject)
    }catch(e){
      this.reject(e)
    }
  }
  // 初始化方法
  initValue(){
    this.PromiseResult = null;
    this.PromiseState = 'pending'
  }
  // 初始化this绑定的方法
  initBind(){
    // 将原型对象的方法始终绑定在实例上
    this.resolve = this.resolve.bind(this)
    this.reject = this.reject.bind(this)
  }
  // resolve方法
  resolve(value) {
      // 这里实现了Promise对象无法改变的功能
      if (this.PromiseState !== 'pending') return
      this.PromiseState = 'fulfilled';
      this.PromiseResult = value;
  }
  // reject方法
  reject(reason) {
      if (this.PromiseState !== 'pending') return
      this.PromiseState = 'rejected'
      this.PromiseResult = reason
  }
  // then方法
  then(onFulfilled, onRejected){
    // 接收两个回调函数onFulfilled, onRejected
    // 进行参数判断,确保是函数
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
    // 随后就是判断Promise的状态分别执行对应的回调
    if (this.PromiseState === 'fulfilled') {
      onFulfilled(this.PromiseResult)
    }else if(this.PromiseState === 'rejected'){
      onRejected(this.PromiseResult)
    }
  }
}

其次,来实现Promise中的异步操作,只要当Promise状态变为fulfilled或rejected状态之一时,才会执行对应的onFulfilled或onRejected方法,当Promise处于pending状态时,那就表示Promise还没有执行完毕,等待状态发生变化时,才能对应的回调函数,因此,我们可以使用数组来保存这些未执行的回调函数。

关键点:

  1. then方法判断Promise的状态。
  2. 保存那些将要执行的回调函数。
class MyPromise{
  constructor(executor){
    // 初始化
    this.initValue();
    // 初始化this绑定
    this.initBind();
    // 执行Promise构造函数传进来的函数,try/catch是对throw方法错误异常的处理
    try{
      executor(this.resolve, this.reject)
    }catch(e){
      this.reject(e)
    }
  }
  // 初始化方法
  initValue(){
    this.PromiseResult = null;
    this.PromiseState = 'pending';
    this.onFulfilledCallbacks = [];// 保存成功的回调
    this.onRejectedCallbacks = []; // 保存失败的回调
  }
  // 初始化this绑定的方法
  initBind(){
    // 将原型对象的方法始终绑定在实例上
    this.resolve = this.resolve.bind(this)
    this.reject = this.reject.bind(this)
  }
  // resolve方法
  resolve(value) {
    // 这里实现了Promise对象无法改变的功能
    if (this.PromiseState !== 'pending') return
    this.PromiseState = 'fulfilled';
    this.PromiseResult = value;
    this.onFulfilledCallbacks.forEach(fn=>fn(this.PromiseResult))
  }
  // reject方法
  reject(reason) {
    if (this.PromiseState !== 'pending') return
    this.PromiseState = 'rejected';
    this.PromiseResult = reason;
    this.onRejectedCallbacks.forEach(fn=>fn(this.PromiseResult))
  }
  // then方法
  then(onFulfilled, onRejected){
    // 接收两个回调函数onFulfilled, onRejected
    // 进行参数判断,确保是函数
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
    // 随后就是判断Promise的状态分别执行对应的回调
    if (this.PromiseState === 'fulfilled') {
      onFulfilled(this.PromiseResult)
    }else if(this.PromiseState === 'rejected'){
      onRejected(this.PromiseResult)
    }else if(this.PromiseState === 'pending'){
      this.onFulfilledCallbacks.push(onFulfilled);// 保存成功的回调
      this.onRejectedCallbacks.push(onRejected);// 保存失败的回调
    }
  }
}

最后,是Promise的链式调用,链式调用的核心要点如下:

  1. 上一个then函数要返回一个Promise对象
  2. 下个then的参数,要拿到上一个then函数的回调返回值
  3. 对回调的返回值进行类型判断,如果是Promise对象,则要对该对象的状态判断执行哪一种方法,如果是其他数据类型则直接执行resolve方法。
  4. 不可以重复返回对象本身

以下代码暂时展示了具体思路,只展示了resolve()方法的改写

then(onFulfilled, onRejected){
  // 接收两个回调函数onFulfilled, onRejected
  // 进行参数判断,确保是函数
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val
  onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
  // 返回Promise状态
  let thenPromise= new MyPromise((resolve,reject)=>{
    // 随后就是判断Promise的状态分别执行对应的回调
    // 注意这里有个坑! 
    // 如果链式调用循环返回同一个Promise,会导致第二Promise对象没有生成就要传入resolvePromise方法
    // 这是因为这是个异步操作,要等到同步操作执行完毕再执行,增加一个setTimeout
    if (this.PromiseState === 'fulfilled') {
      setTimeOut(()=>{
        let x = onFulfilled(this.PromiseResult);
        // 这里对返回值x进行判断,如果是Promise对象要判断Promise的状态
        // 如果是一个普通值,直接用resolve方法返回
        resolvePromise(x,resolve,reject,thenPromise);
      },0)
    }else if(this.PromiseState === 'rejected'){
      onRejected(this.PromiseResult)
    }else if(this.PromiseState === 'pending'){
      this.onFulfilledCallbacks.push(onFulfilled);// 保存成功的回调
      this.onRejectedCallbacks.push(onRejected);// 保存失败的回调
    }
  })
  return thenPromise
}
function resolvePromise(x, resolve, reject,thenPromise){
  // 防止循环调用Promise,不可以返回本身
  if(x === thenPromise){
    return reject(new TypeError('不可以返回本身!'))
  }
  // 然后判断这个x是否是一个Promise对象,可以使用instanceOf方法
  if(x instanceof MyPromise){
    // 完整写法
    // x.then((value)=>{
    //   resolve(value);
    // },err=>{
    //   reject(err);
    // })
    x.then(resolve, reject)
  }else{
    // 普通值
    resolve(x)
  }
}

测试用例

  1. 测试Promise处于fulfilled状态下执行onFulfiled方法和处于rejected状态下执行onRejected方法
const test5 = new MyPromise((resolve, reject)=>{
    resolve('成功')
}).then(res => console.log(res), err => console.log(err))

const test6 = new MyPromise((resolve, reject)=>{
    reject('失败')
}).then(res => console.log(res), err => console.log(err))

测试结果如下:

  1. 测试Promise异步操作
const test7 = new MyPromise((resolve, reject)=>{
    setTimeout(() => {
        resolve('成功')
    }, 1000);
}).then(res=>console.log(res), err => console.log(err))

测试结果如下:

  1. then方法的链式调用

测试用例如下:

const test8 = new MyPromise((resolve, reject) => {
        resolve(500)
    }).then((res) => {
        console.log(res)
        return 200
    }).then((data) => {
        console.log(data)
    })
    const test9 = new MyPromise((resolve, reject) => {
        resolve(500)
    }).then((res) => {
        console.log(res)
        return new MyPromise((resolve, reject) => {
            resolve(200)
        })
    }).then((data) => {
        console.log(data)
    })
// 链式调用时,返回本身
let p = new MyPromise((resolve, reject) => {
      resolve(300)
  })
  let p2 = p.then((res) => {
      console.log(res)
      return p2
  })
  p2.then((res) => {
      console.log(res)
  })

测试结果:

实现Promise的完整代码

class MyPromise{
    constructor(executor){
        console.log(executor)
        // 初始化值
        this.initValue()
        // 初始化this执行
        this.initBind()
        // 执行传进来的函数,try/catch是对throw方法错误异常的处理
        try{
            executor(this.resolve, this.reject)
        }catch(e){
            this.reject(e)
        }
    }
    initValue(){
        this.PromiseResult = null;
        this.PromiseState = 'pending';
        this.onFulfilledCallbacks = [];// 保存成功的回调
        this.onRejectedCallbacks = [];// 保存失败的回调
    }
    initBind(){
        this.resolve = this.resolve.bind(this)
        this.reject = this.reject.bind(this)
    }
    resolve(value){
        if(this.PromiseState!== 'pending') return
        this.PromiseState = 'fulfilled';
        this.PromiseResult = value;
        this.onFulfilledCallbacks.forEach((fn)=>fn())
        // while(this.onFulfilledCallbacks.length){
        //     this.onFulfilledCallbacks.shift()(this.PromiseResult)
        // }
    }
    reject(reason){
        if(this.PromiseState!== 'pending') return
        this.PromiseState = 'rejected'
        this.PromiseResult = reason
        this.onRejectedCallbacks.forEach((fn)=>fn())
        // while(this.onRejectedCallbacks.length){
        //     this.onRejectedCallbacks.shift()(this.PromiseResult)
        // }
    }
    // 实现then方法
    then(onFulfilled, onRejected){
        // 分别给onFulfilled和onRejected方法进行初始化
        // 然后根据Promise的状态执行对应的方法
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
        // 返回Promise对象的操作 .then链式调用的核心
        var thenPromise = new MyPromise((resolve, reject)=>{
            if(this.PromiseState === 'fulfilled'){
                // 核心是判断是否为返回值是否为Promise,是那么要等待其完成,如果不是那么直接onFulfilled方法执行完,resolve直接结束
                // 为了防止thenPromise没有生成 要采用异步操作
                setTimeout(() => {
                    try {
                        let x = onFulfilled(this.PromiseResult)
                        resolvePromise(x, resolve, reject, thenPromise)
                    } catch (error) {
                        reject(error)
                    }
                }, 0);
            }else if(this.PromiseState === 'rejected'){
                setTimeout(() => {
                    try {
                        let x = onRejected(this.PromiseResult)
                        resolvePromise(x, resolve, reject, thenPromise)
                    } catch (error) {
                        reject(error)
                    }
                }, 0);
            }else if(this.PromiseState === 'pending'){
                // 这里是当Promise的状态为pending,将这些回调函数保存在数组中
                // 注意这里回调函数的指向,始终指向的是回调函数的执行者
                this.onFulfilledCallbacks.push(()=>{
                    setTimeout(() => {
                        try {
                            let x = onFulfilled(this.PromiseResult)
                            resolvePromise(x, resolve, reject, thenPromise)
                        } catch (error) {
                            reject(error)
                        }
                    }, 0);
                })// bind绑定保证了this始终指向then方法的调用者,保证值不丢失
                this.onRejectedCallbacks.push(()=>{
                    setTimeout(() => {
                        try {
                            let x = onRejected(this.PromiseResult)
                            resolvePromise(x, resolve, reject, thenPromise)
                        } catch (error) {
                            reject(error)
                        }
                    }, 0);
                })
            }
        })
        // 返回一个包装的Promise
        return thenPromise
    }
}

function resolvePromise(x, resolve, reject, thenPromise){
    if(x === thenPromise){
        return reject(new TypeError('不可以返回本身!'))
    }
    // 防止多次调用
    let called;
    // x不是Null且x是对象或者函数
    if(x !== null && (typeof x === 'object' || typeof x=== 'function')){
        try {
            // A+ 规定,声明then = x的then方法
            let then = x.then
            // 如果then是函数,就默认是promise了
            if(typeof then === 'function'){
                // 就让then执行,第一个参数是this 后面是成功的回调和失败的回调
                then.call(x,y=>{
                    // 失败和成功的回调只能调用一个
                    if(called) return;
                    called = true;
                    // resolve出来的结果是Promise,那么继续解析
                    resolvePromise(y, resolve, reject, thenPromise)
                },err=>{
                    if(called) return;
                    called = true;
                    // 直接执行reject函数
                    reject(err);
                })
            }
        } catch (error) {
            // 也属于失败了
            if(called) return;
            called = true;
            // 去then出错了那么就不要在继续执行了
            reject(e)
        }
    }else{
        resolve(x)
    }
}

Promise其他重要方法

resolve()&reject()方法

resolve()与reject()方法分别是用来返回一个Promise对象,Promise对象中执行了对应的resolve与reject方法。

// resolve()方法
MyPromise.resolve = function(val){
  return new Promise((resolve, reject)=>{
    resolve(val);
  })
}
// reject()方法
MyPromise.reject = function(val){
  return new Promise((resolve, reject)=>{
    reject(val);
  })
}
// 测试用例如下:
MyPromise.resolve(200).then(data =>{
    console.log(data) // 200
})
MyPromise.reject(200).then(data =>{
    console.log(data)
},
err=>{
    console.log(err) // 200
})

race()方法

race()方法遵循竞速原则,只要其中有一个Promise对象状态发生改变,就优先返回先改变状态的Promise对象。

MyPromise.race = function(promises){
  // 遍历数组中的Promise对象,谁先满足条件你谁先执行resolve方法,返回resolve方法这个值
  return new MyPromise((resolve,reject)=>{
    for(let i = 0;i < promises.length; i++){
      if(promises[i] instanceof MyPromise){
        promises[i].then(resolve, reject) 
      }else{
        resolve(promises[i]);
      }
    }
  })
}
const p1 = new MyPromise((resolve,reject)=>{
    setTimeout(() => {
        resolve(300)
    }, 3000);
})
const p2 = new MyPromise((resolve,reject)=>{
    setTimeout(() => {
        resolve(200)
    }, 2000);
})
const p3 = new MyPromise((resolve,reject)=>{
    setTimeout(() => {
        resolve(300)
    }, 1000);
})
MyPromise.race([p1,p2,p3]).then((val)=>{
    console.log(val) // 返回 300
},(reason)=>{
    console.log(reason)
})

all()方法

Promise.all遵循等待原则,等到所有的Promises都满足了条件之后,拿到所有成功的结果。而且拿到的结果是按照满足条件的顺序来展示的

// 接受的是一个数组
MyPromise.all = function(promises){
  let arr = [];
  let i = 0;//用于计数,保证i === promises.length时,退出循环
  function processData(res, index, resolve){
    arr[index] = res;
    i++;
    if(i === promises.length){
      resolve(arr);
    }
  }
  return new MyPromise((resolve, reject)=>{
    for(let j=0;j<promises.length;j++){
      if(promises[j] instanceof Mypromise){
        promises[j].then((res)=>{
          processData(res,j,resolve)
        },(reason)=>{
          reject(reason)
        })
      }else{
        processData(promises[j], j);
      }
    }
  })
}
const p1 = new MyPromise((resolve,reject)=>{
    setTimeout(() => {
        resolve(300)
    }, 3000);
})
const p2 = new MyPromise((resolve,reject)=>{
    setTimeout(() => {
        resolve(200)
    }, 2000);
})
const p3 = new MyPromise((resolve,reject)=>{
    setTimeout(() => {
        resolve(300)
    }, 1000);
})
MyPromise.race([p1,p2,p3]).then((val)=>{
    console.log(val) // 返回 300
},(reason)=>{
    console.log(reason)
})

allSettled()方法

等到Promise数组中所有的Promise全部完成不论是成功还是失败。

MyPromise.allSettled = function(promises){
        return new MyPromise((resolve, reject)=>{
            let arr = [];
            let count = 0;
            function processData(status,res,index){
                arr[index] = {status,value:res};
                count++;
                if(count === promises.length){
                    resolve(arr);
                }
            }
            promises.forEach((item,index)=>{
                if(item instanceof MyPromise){
                    item.then(res=>{
                        processData('fulfilled',res,index);
                    },err=>{
                        processData('rejected',err,index);
                    })
                }else{
                    processData('fulfilled',item,index);
                }
            })
        })
    }
MyPromise.allSettled([p1,p2,p3]).then((arr)=>{
    console.log(arr)
},(reason)=>{
    console.log(new Error(reason))
})

any()方法

当所有的Promise对象都报错了,该方法会返回报错的原因

MyPromise.any = function(promises){
    return new MyPromise((resolve,reject)=>{
        let count = 0;
        promises.forEach((item)=>{
            if(item instanceof MyPromise){
                item.then((res)=>{
                    resolve(res)
                },(err)=>{
                    count++;
                    if(count === promises.length){
                        reject('Promise都有问题')
                    }
                })
            }else{
                resolve(item)
            }
        })
    })
}
const p4 = new MyPromise((resolve,reject)=>{
    setTimeout(() => {
        reject(300)
    }, 200);
})
const p5 = new MyPromise((resolve,reject)=>{
    setTimeout(() => {
        reject(400)
    }, 300);
})
const p6 = new MyPromise((resolve,reject)=>{
    setTimeout(() => {
        reject(500)
    }, 400);
})
MyPromise.any([p4,p5,p6]).then(()=>{},(reason)=>{
    console.log(reason);
})

总结

经过这次 Promise原理的学习,让我深入理解了promise的then方法的链式调用,异步操作的使用与处理,数据类型的判断。对我的JS基础方面解析进一步的巩固。

参考资料

林三心的Promise原理解析

黑马程序员Promise原理解析