Promise的理解

259 阅读8分钟

Promise

面试:说说你对promise的理解?

我:经常用,promise是一个构造函数,用new来声明,promise传入两个参数resove和reject,then接收resolve里边的参数,catch接收reject里边的参数。

完了?? 完了。。。

好好看看吧。怎么才能答出面试官比较满意的答案呢?我们先手写一个深入了解一下吧。

基本使用

首先要了解:then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。

看一个promise的基本使用:

const promise = new Promise((resolve, reject) => {
  resolve('success');
  reject('error');
})

promise.then(res => {
  console.log(res);
}, err => {
  console.log(err);
});

一、首先我们先实现以下基本原理:

  1. Promise 是一个类,在执行这个类的时候会传入一个执行器,这个执行器会立即执行
  1. Promise 会有三种状态
  • Pending 等待
  • Fulfilled 完成
  • Rejected 失败
  1. 状态只能由 Pending --> Fulfilled 或者 Pending --> Rejected,且一但发生改变便不可二次修改;
  1. Promise 中使用 resolve 和 reject 两个函数来更改状态;
  1. then 方法内部做但事情就是状态判断
  • 如果状态是成功,调用成功回调函数
  • 如果状态是失败,调用失败回调函数
// 先定义三个常量表示状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MyPromise {
  constructor(executor) {
    // executor 是一个执行器,进入会立即执行
    executor(this.resolve, this.reject);
  }

  status = PENDING;

  // 成功之后的值
  value = null;
  // 失败之后的原因
  reason = null;

  // resolve和reject为什么要用箭头函数?
  // 如果直接调用的话,普通函数this指向的是window或者undefined
  // 用箭头函数就可以让this指向当前实例对象
  // 更改成功后的状态
  resolve = (value) => {
    if (this.status === PENDING) {
      this.status = FULFILLED;

      this.value = value;
    }
  }
  // 更改失败后的状态
  reject = (reason) => {
    // 只有状态是等待,才执行状态修改
    if (this.status === PENDING) {
      // 状态成功为失败
      this.status = REJECTED;
      // 保存失败后的原因
      this.reason = reason;
    }
  }

  then(onFulfilled, onRejected) {
    // 判断状态
    if (this.status === FULFILLED) {
      // 调用成功回调,并且把值返回
      onFulfilled(this.value);
    } else if (this.status === REJECTED) {
      // 调用失败回调,并且把原因返回
      onRejected(this.reason);
    }
  }
}

// 测试我们上边写的promise
const promise = new MyPromise((resolve, reject) => {
  resolve('success');
  reject('error');
})

promise.then((res) => {
  console.log(res);
}, (err) => {
  console.log(err);
})

二、处理异步

如果我们要执行异步操作,如下,就有问题了

const promiseAsync = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
  }, 1000); 
})

promiseAsync.then(value => {
  console.log('resolve', value)
}, reason => {
  console.log('reject', reason)
})

结果没有打印信息

分析:主线程代码立即执行,setTimeout 是异步代码,then 会马上执行,这个时候判断 Promise 状态,状态是 Pending,然而之前并没有判断等待这个状态

改造思路,处理一下Pending状态

// 新增存储成功回调函数
onFulfilledCallback = null;
// 存储失败回调函数
onRejectedCallback = null;

// 如果是异步的话就会先走then,再走resolve/reject,所以先存一下成功/失败的回调
  then(onFulfilled, onRejected) {
    // 判断状态 新增判断PENDING状态
    if (this.status === PENDING) {
      onFulfilledCallback = onFulfilled;
      onRejectedCallback = onRejected;
    } else if (this.status === FULFILLED) {
      // 调用成功回调,并且把值返回
      onFulfilled(this.value);
    } else if (this.status === REJECTED) {
      // 调用失败回调,并且把原因返回
      onRejected(this.reason);
    }
  }


resolve = (value) => {
    if (this.status === PENDING) {
      this.status = FULFILLED;

      this.value = value;

      // 如果有成功回调函数,就执行
      onFulfilledCallback && onFulfilledCallback(value);
    }
  }
  // 更改失败后的状态
  reject = (reason) => {
    // 只有状态是等待,才执行状态修改
    if (this.status === PENDING) {
      // 状态成功为失败
      this.status = REJECTED;
      // 保存失败后的原因
      this.reason = reason;

      // 如果有失败回调函数,就执行
      onRejectedCallback && onRejectedCallback(reason);
    }
  }

改造完成,让我们试一下上边的代码,有结果了:1s后输出 resolve success

三、实现then多次调用处理函数

Promise 的 then 方法是可以被多次调用的。这里如果有三个 then 的调用,如果是同步回调,那么直接返回当前的值就行;如果是异步回调,那么保存的成功失败的回调,需要用不同的值保存,因为都互不相同。之前的代码需要改进。

先看一个🌰

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

promise.then(value => {
  console.log(1)
  console.log('resolve', value)
})
 
promise.then(value => {
  console.log(2)
  console.log('resolve', value)
})

promise.then(value => {
  console.log(3)
  console.log('resolve', value)
})

结果:

3

resolve success

分析:因为我们对onFulfilledCallback/onRejectedCallback是直接赋值,所以只会执行最后的一次

所以需要继续改造,保证所有的then全部执行(这里将储存成功/失败的回调放在了class里边,记得更改访问方式为this.onFulfilledCallback)

// 先定义三个常量表示状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MyPromise {
  constructor(executor) {
    // executor 是一个执行器,进入会立即执行
    executor(this.resolve, this.reject);
  }

  // 新增存储成功回调函数,将其变成数组
  onFulfilledCallback = [];
  // 存储失败回调函数,将其变成数组
  onRejectedCallback = [];

  status = PENDING;

  // 成功之后的值
  value = null;
  // 失败之后的原因
  reason = null;

  // resolve和reject为什么要用箭头函数?
  // 如果直接调用的话,普通函数this指向的是window或者undefined
  // 用箭头函数就可以让this指向当前实例对象
  // 更改成功后的状态
  resolve = (value) => {
    if (this.status === PENDING) {
      this.status = FULFILLED;

      this.value = value;

      // 如果有成功回调函数,就执行
      // 循环执行
      while (this.onFulfilledCallback.length) {
        // Array.shift() 取出数组第一个元素,然后()调用,shift不是纯函数,取出后,数组将失去该元素,直到数组为空
        this.onFulfilledCallback.shift()(value);
      }
    }
  }
  // 更改失败后的状态
  reject = (reason) => {
    // 只有状态是等待,才执行状态修改
    if (this.status === PENDING) {
      // 状态成功为失败
      this.status = REJECTED;
      // 保存失败后的原因
      this.reason = reason;

      // 如果有失败回调函数,就执行
      // 循环执行
      while (this.onRejectedCallback.length) {
        this.onRejectedCallback.shift()(reason);
      }
    }
  }
  // 如果是异步的话就会先走then,再走resolve/reject,所以先存一下成功/失败的回调
  then(onFulfilled, onRejected) {
    // 判断状态 新增判断PENDING状态
    if (this.status === PENDING) {
      this.onFulfilledCallback.push(onFulfilled);
      this.onRejectedCallback.push(onRejected);
    } else if (this.status === FULFILLED) {
      // 调用成功回调,并且把值返回
      onFulfilled(this.value);
    } else if (this.status === REJECTED) {
      // 调用失败回调,并且把原因返回
      onRejected(this.reason);
    }
  }
}

再次执行,结果:

1

resolve success

2

resolve success

3

resolve success

完美~

四、实现then的链式调用(promise.then().then())

同样,我们先看一个例子:

const promise = new MyPromise((resolve, reject) => {
  // 目前这里只处理同步的问题
  resolve('success')
})

function other () {
  return new MyPromise((resolve, reject) =>{
    resolve('other')
  })
}

promise.then(value => {
  console.log(1)
  console.log('resolve', value)
  return other()
}).then(value => {
  console.log(2)
  console.log('resolve', value)
})

结果:

Uncaught TypeError TypeError: Cannot read property 'then' of undefined

思考:如果是then().then()的话,then()应该也是一个promise,so 改造then

...
// 如果是异步的话就会先走then,再走resolve/reject,所以先存一下成功/失败的回调
  then(onFulfilled, onRejected) {
    const resPromise = new MyPromise((resolve, reject) => {
      // 判断状态 新增判断PENDING状态
      if (this.status === PENDING) {
        this.onFulfilledCallback.push(onFulfilled);
        this.onRejectedCallback.push(onRejected);
      } else if (this.status === FULFILLED) {
        // 改造这里then嘛,肯定是成功的回调,获取成功回调的结果
        const x = onFulfilled(this.value);
        // 传入resolvePromise统一处理
        resolvePromise(x, resolve, reject);
      } else if (this.status === REJECTED) {
        // 调用失败回调,并且把原因返回
        onRejected(this.reason);
      }
    })
    return resPromise;
  }

function resolvePromise(x, resolve, reject) {
  // 如果x是一个promise,那么就调用x的then方法,并且把resolve和reject传进去
  if (x instanceof(MyPromise)) {
    x.then(resolve, reject);
  } else {
    resolve(x);
  }
}

结果:

1

resolve success

2

resolve other

五、then方法链式调用识别Promise是否返回自己

举例:

const promise = new Promise((resolve, reject) => {
  resolve(100)
})
const p1 = promise.then(value => {
  console.log(value)
  return p1
})

使用原生Promise执行这个代码,会报类型错误

100
Uncaught (**in** promise) TypeError: Chaining cycle detected **for** promise #<Promise>

在我们的MyPromise实现一下:

class MyPromise {
  ......
  then(onFulfilled, onRejected) {
    const promise2 = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        const x = onFulfilled(this.value);
        // resolvePromise 集中处理,将 promise2 传入
        resolvePromise(promise2, x, resolve, reject);
      } else if (this.status === REJECTED) {
        onRejected(this.reason);
      } else if (this.status === PENDING) {
        this.onFulfilledCallbacks.push(onFulfilled);
        this.onRejectedCallbacks.push(onRejected);
      }
    }) 
    
    return promise2;
  }
}

function resolvePromise(promise2, x, resolve, reject) {
  // 如果相等了,说明return的是自己,抛出类型错误并返回
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
  }
  if(x instanceof MyPromise) {
    x.then(resolve, reject)
  } else{
    resolve(x)
  }
}

执行,结果报错了

resolvePromise(promise2, x, resolve, reject);
                       ^

ReferenceError: Cannot access 'promise2' before initialization

分析:从报错来看,我们必须要等 promise2 完成初始化。这个时候我们就要用上宏微任务和事件循环的知识了,这里就需要创建一个异步函数去等待 promise2 完成初始化,我们采用创建微任务的技术方案 --> queueMicrotask

// MyPromise.js

class MyPromise {
  ......
  then(onFulfilled, onRejected) {
    const promise2 = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        // ==== 新增 ====
        // 创建一个微任务等待 promise2 完成初始化
        queueMicrotask(() => {
          // 获取成功回调函数的执行结果
          const x = onFulfilled(this.value);
          // 传入 resolvePromise 集中处理
          resolvePromise(promise2, x, resolve, reject);
        })  
      } else if (this.status === REJECTED) {
      ......
    }) 
    
    return promise2;
  }
}

OK,改造完成,再次执行上边的结果:

1
resolve success
3
Chaining cycle detected for promise #<Promise>

还有很多功能没有实现,到这里就先不做后续处理了,感兴趣的可以参考: juejin.cn/post/694531…

总结:

  1. Promise 是一个类,在执行这个类的时候会传入一个执行器,这个执行器会立即执行
  1. Promise 会有三种状态
  • Pending 等待
  • Fulfilled 完成
  • Rejected 失败
  1. 状态只能由 Pending --> Fulfilled 或者 Pending --> Rejected,且一但发生改变便不可二次修改;
  1. Promise 中使用 resolve 和 reject 两个函数来更改状态;
  1. then 方法内部做但事情就是状态判断
  • 如果状态是成功,调用成功回调函数
  • 如果状态是失败,调用失败回调函数
  1. Promise.then()执行之后得到的还是一个Promise,从而实现了链式调用
  2. Promise是用来解决回调地狱的

补充其缺点

  1. 首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。
  2. 其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
  3. 第三,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

如何取消正在执行的promise

真的有面试官问这种问题。。。

我们都知道promise一旦执行,则无法取消,所以需要我们自己封装了。我们可以借助Promise.race()的方法,如果不知道这个方法的,可以移步es6.ruanyifeng.com/?search=rac…

//传入一个正在执行的promise
function getPromiseWithAbort(p){
    let obj = {};
    //内部定一个新的promise,用来终止执行
    let p1 = new Promise(function(resolve, reject){
        obj.abort = reject;
    });
    obj.promise = Promise.race([p, p1]);
    return obj;
}

调用

var promise  = new Promise((resolve)=>{
 setTimeout(()=>{
  resolve('123')
 },3000)
})
 
var obj = getPromiseWithAbort(promise)
 
obj.promise.then(res=>{console.log(res)})
 
//如果要取消
obj.abort('取消执行')