Promise A+/手写Promise

165 阅读8分钟

Promise 应用场景

异步处理数据:如网络请求超时提醒,异步加载数据等

原理:事件轮询机制 Event Loop

浏览器处理时的先后顺序:同步任务执行完毕之后会在回调队列中轮询,有新的异步任务开始推入回调栈内进行执行。

四个执行模块:Browser Console,Call Stack, Web APIs, Callback Queue

console.log('start');

setTimeout(function cb() => {
    console.log('async');
}, 1000);

console.log('end');

执行细节如下:

  1. console.log('start') 推入调用栈,执行完毕后清空,打印start
  2. 调用栈执行setTimeout,setTimeout是浏览器定义的,所以timer会在Web APIs中,参数cb,也在其中。1秒中之后把cb放入Callback Queue中
  3. setTimeout执行完毕,清空调用栈
  4. 执行最后一行代码,推到调用栈中,调用栈执行,浏览器控制台打印end
  5. 同步代码执行完毕,启用event loop 在回调栈中轮询任务
  6. 轮询到之前推入的cb,将其推入调用栈执行
  7. cb中的console.log('async'),推入调用栈并执行打印async
  8. console.log('async')执行完毕,清空;cb执行完毕,清空

宏任务队列可以有多个,微任务队列只有一个(浏览器为了能够使得JS内部(macro)task与DOM任务能够有序的执行,会在一个(macro)task执行结束后,在下一个(macro)task 执行开始前,对页面进行重新渲染)。

宏任务:script(全局任务), setInterval, setTimeout, setImmediate, I/O, UI 事件.
(消息队列,添加在执行栈的尾部)
微任务:process.nextTick, Promise, Object.observer, MutationObserver.
(作业队列, 优先级高于宏任务) 引用参考自火锅小王子00

术语

  1. promise 是一个有then方法的对象或函数。行为遵循本规范
  2. thenable 是一个then方法的对象或函数。
  3. value 是promise状态成功时的值resolve 的参数 类型可以时undefined / thenable promise, number, boolean
  4. reason 是promise 状态失败的值,reject的参数,表示拒绝的原因
  5. exception 是一个使用throw抛出的异常值

规范

Promise States

promise应该有三种状态:注意他们之间的流转关系

  1. pending

    1. 初始状态,可改变

    2. 一个Pormise在resolve和reject之前 都处于这个状态

    3. resolve函数:pending -> fulfilled

    4. reject函数:pending -> rejected

      Promise 的状态完全由上述两个函数控制

  2. fulfieled

    1. 最终态,不可改变
    2. 一个Promise被resolve之后变成此状态
    3. 必须有个value值;默认undefined
  3. rejected

    1. 最终态,不可改变
    2. 一个Promise被reject之后变成此状态
    3. 必须有个reason值;默认undefined

状态流转路线:

pending --> fulfilled
pending --> rejected

then

promise 应该提供一个then方法,用来访问最终结果:访问value

then方法接受两个参数:

promise.then(onFulfilled, onRejected)
  1. 参数要求

    1. onFulfilled 必须是函数类型,如果不是函数,应该被忽略

    2. onRejected 必须是函数类型,如果不是函数,应该被忽略

      忽略:如果不是函数,会给一个默认的函数

  2. onFulfilled 特性

    1. 在 promise 变成 fulfilled 状态时,应该调用 onFulfilled,参数是value
    2. 在 promise 变成 fulfilled 之前,不应该被调用
    3. 只能被调用一次(可能需要一个变量限制执行次数)
  3. onRejected 特性

    1. 在 promise 变成 rejected 状态时,应该调用 onRejected,参数是reason
    2. 在 promise 变成 rejected 之前,不应该被调用
    3. 只能被调用一次(可能需要一个变量限制执行次数)
  4. onFulfilled 和 onRejected 应该都是微任务

浏览器封装了queueMicrotask 来实现微任务的调用

queueMicrotask () => {}
  1. then方法可以被调用多次

    const promise = new Promise();
    promise.then(cb1, cb2);
    promise.then(cb1, cb2);
    promise.then(cb1, cb2);
    promise.then(cb1, cb2);
    
    1. promise 状态变成fulfulled之后,所有的onFulfilled回调都需要按照then的顺序执行

    2. promise 状态变成rejected之后,所有的onRejected回调都需要按照then的顺序执行

      二者的都需要数组去存:

      [onFulfilled1, onFulfilled2, onFulfilled3]
      [onRejected1, onRejected2, onRejected3]
      
  2. then 的返回值

    then返回值是一个Promise,并且是一个新的Promise

    const promise1 = new Promise();
    const promise2 = promise1.then(cb1, cb2)
    
    // 这里的promise2是个新的Promise
    // 因此,链式调用并不是返回第一个promise 的最终态,而是和最后一个then有关
    
    1. onFulfilled 或者 onRejected 执行结果为x,调用resolvePromise(): 解析promise
    2. onFulfilled 或者 onRejected 执行时报错了,promise2就需要被reject
    3. onFulfilled 不是一个函数,会做透传,promise2 以 promise1 的value,触发fulfilled
    4. onRejected 不是一个函数,会做透传,promise2 以 promise1 的reason,触发rejected
  3. resolvePromise 意义在于对promise 各种值的处理

    resolvePromise(promise2, x, resolve, reject)
    
    1. 如果 promise2 === x, reject typeError 这是为了防止死循环

    2. 如果x是一个promise

      如果是pending -> promise2 也的是 pending,知道x状态变化

      如果是fulfilled -> promise2 以相同的value fulfilled

      如果是rejected -> promise2 以相同的reason rejected

    3. 如果x是一个object/function

      去获取 then let then = x.then; 看是否报错

      then如果是一个函数,then.call(x, resolvePromiseFn, rejectePromiseFn)

一步步实现一个Promise

  1. 初始化class

    class MPromise {
      constructor() {
        
      }
    }
    
  2. 定义三种状态类型

    const PENDING = 'pending';
    const FULFILLED = 'fulfilled';
    const REJECTED = 'rejected';
    
  3. 设置初始状态:

    class MPromise {
      constructor() {
        this.status = PENDING;
        this.value = null;
        this.reason = null;
      }
    }
    
  4. 两个改变状态的方法:resolve/reject

    1. 更改status,pending -> fulfilled, rejected

    2. 入参value/reason

    class MPromise {
      constructor() {
        this.status = PENDING;
        this.value = null;
        this.reason = null;
      }
      
      resolve(value) {
        // 约束最终态不可变
        if (this.status === PENDING) {
          this.value = value;
          this.status = FULFILLED;
        }   
      }
      
      rejected(reason) {
        if (this.status === PENDING) {
          this.reason = reason;
          this.status = REJECTED;
        }  
      }
    }
    
  5. promise构造函数的入参

    new Promise((resolve, reject) => {})
    
    1. 入参是一个函数,函数接受两个参数,resolve,reject

    2. new Promise的时候,就要立即执行这个函数,并且有任何错误都要被reject出去

      console.log('begin'); // 同步任务
      new Promise((resolve, reject) => {
        console.log(1) // 同步任务,new Promise之后立即执行
      }).then(() => {
        console.log(2) // .then才会被称为微任务
      })
      
    class MPromise {
      constructor(fn) {
        this.status = PENDING;
        this.value = null;
        this.reason = null;
        
        // 需要被立即执行
        // 注意this绑定
        // 一旦报错,reject
        try {
          fn(this.resolve.bind(this), this.reject.bind(this));
        } catch(e) {
          this.reject(e)
        }
      }
      
      resolve(value) {
        // 约束最终态不可变
        if (this.status === PENDING) {
          this.value = value;
          this.status = FULFILLED;
        }   
      }
      
      rejected(reason) {
        if (this.status === PENDING) {
          this.reason = reason;
          this.status = REJECTED;
        }  
      }
    }
    
  6. then方法,两个参数onFulfilled, onRejected,返回一个promise

    then(onFulfilled, onRejected) {
      const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (value) => {
        return value; // 透传,接收什么返回什么
      };
      
       const realOnRejected = this.isFunction(onRejected ) ? onRejected : (reason) => {
        throw reason; // 透传,接收什么返回什么
      };
      
      const promise2 = new MPromise((resolve, reject) => {
        switch(this.status) {
          case FULFILLED: {
            realOnFulfilled();
            break;
          }
          case REJECTED: {
            realOnRejected();
            break;
          }
        }
      });
      return promise2;
    }
    
    // 辅助函数判断入参是否为函数
    isFunction(param) {
      return typeof param === 'function';
    }
    

    以上代码只能够处理同步执行:

    new Promise((resolve, reject) => {
      console.log(1);
      resolve();
    }).then(value => )
    // 当内部有异步的情况的话
    new Promise((resolve, reject) => {
      console.log(1);
     setTimout(() => {}, 1000) // 这里还是pending
    }).then(value => ) 
    

    因此,需要数组去存;此外还因为then可以被多次调用

    FULFILLED_CALLBACK_LIST = [];
    REJECTED_CALLBACK_LIST = [];
    
     const promise2 = new MPromise((resolve, reject) => {
        switch(this.status) {
          case FULFILLED: {
            realOnFulfilled();
            break;
          }
          case REJECTED: {
            realOnRejected();
            break;
          }
          case PENDING: {
            this.FULFILLED_CALLBACK_LIST.push(realOnFulfilled);
            this.REJECTED_CALLBACK_LIST.push(realOnRejected);
          }
        }
      });
      return promise2;
    }
    

    状态改变时,执行回调:通过getter,setter监听状态变化

    _status = PENDING;
    // 暴露给其他函数使用
    get status() {
      return this._status;
    }
    
    set status(newStatus) {
      this._status = newStatus;
      switch(newStatus) {
        case FULFILLED: {
          this.FULFILLED_CALLBACK_LIST.forEach(callback => {
            callback(this.value);
          })
          break;
        }
        case REJECTED: {
          this.REJECTED_CALLBACK_LIST.forEach(callback => {
            callback(this.reason);
          })
          break;
        }
      }
    }
    

    需要一个私有变量存改变的状态,否则死循环。

  7. 处理onFulfilled,onRejected的值 - 应该是微任务;返回值为x,x调用resolvePromise函数

    const promise2 = new MPromise((resolve, reject) => {
    	const fulfilledMicrotask = () => {
        try {
          const x =  realOnFilfilled(this.value);
          this.resolvePromise(promise2, x, resolve, reject);
        } catch(e) {
          reject(e);
        }
      }
      
      const rejectedMicrotask = () => {
        try {
          realOnRejected(this.reason);
          this.resolvePromise(promise2, x, resolve, reject);
        } catch(e) {
          reject(e);
        }
      }
      
      switch(this.status) {
        case FULFILLED: {
          fulfilledMicrotask();
          break;
        }
        case REJECTED: {
          rejectedMicrotask();
          break;
        }
        case PENDING: {
            this.FULFILLED_CALLBACK_LIST.push(fulfilledMicrotask);
            this.REJECTED_CALLBACK_LIST.push(rejectedMicrotask);
          }
      }
    });
    
    
  8. resolvePromise

    const promise2 = new MPromise((resolve, reject) => {
    	const fulfilledMicrotask = () => {
        // 包装queueMicrotask
        queueMicrotask(() => {
          try {
            // 得到x
            const x =  realOnFilfilled(this.value);
            this.resolvePromise(promise2, x, resolve, reject);
          } catch(e) {
            reject(e);
          }
        })
      }
      
      const rejectedMicrotask = () => {
        const fulfilledMicrotask = () => {
        // 包装queueMicrotask
        queueMicrotask(() => {
          try {
            // 得到x
            const x =  realOnRejected(this.reason);
            this.resolvePromise(promise2, x, resolve, reject);
          } catch(e) {
            reject(e);
          }
        })
      }
      
      switch(this.status) {
        case FULFILLED: {
          fulfilledMicrotask();
          break;
        }
        case REJECTED: {
          rejectedMicrotask();
          break;
        }
        case PENDING: {
            this.FULFILLED_CALLBACK_LIST.push(fulfilledMicrotask);
            this.REJECTED_CALLBACK_LIST.push(rejectedMicrotask);
          }
      }
    });
    
    
    resolvePromise(promise2, x, resolve, reject) {
      if (promise2 === x) {
        return reject(new TypeError('The promise and the return value are the same!'))
      }
      // 如果x是一个promise,是否时对象
      if (x instanceof MPromise) {
        queueMicrotask(() => {
          x.then(
            (y) => {
              this.resolvePromise(promise2, y, resolve, reject)
            },
            reject
          )
        })
      } else if (typeof === 'object' || this.isFunction(x)) {
        if (x === null) {
          return resolve(x);
        }
         // 规范中提到,但是不怎么发生,提出x的then属性
        let then = null;
        
        try {
          then = x.then;
        } catch(error) {
          return reject(error)
        }
        
        // 判断 then 是否是函数
        if (this.isFunction(then)) {
          // 限制回调次数
          let called = false;
          try {
            then.call(
            	x,
              (y) => {
                if (called) {
                  return;
                }
                called = true;
                this.resolvePromise(promise2, y, resolve, reject);
              },
              (r) => {
                if (called) {
                  return;
                }
                called = true;
                reject(r);
              }
            )
          } catch(error) {
            if (called) {
              return;
            }
            reject(error);
          }
        } else {
          resolve(x);
        }
      } else {
        resolve(x);
      }
    }
    
  9. catch方法

    catch(onRejected) {
      return this.then(null, onRejected);
    }
    
  10. 静态方法resolve,reject

    // MPromise.resolve(); 
    // MPromise.reject();
    static resolve(value) {
      if (value instanceof MPromise) {
        return value;
      }
      
      return new MPromise((resolve) => {
        resolve(value);
      })
    }
    
    static reject(reason) {  
      return new MPromise((resolve, reject) => {
        reject(reason);
      })
    }
    

测试:

const test = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(111);
  }, 1000);
}).then((value) => {
  console.log(`[then] value = ${value}`)
}).catch((reason) => {
  console.log(`[catch] reason = ${reason}`)
})

console.log(test)

setTimeout(() => {
  console.log(test)
}, 2000)

关于回调函数数组储存:

  1. 链式调用没有意义 -->每次返回一个新的promise,因此数组为空

  2. 但基于如下情况的使用有意义:

    test.then(() => {});
    test.then(() => {});
    test.then(() => {});
    test.then(() => {});
    

关于then, catch都返回新的promise

const test = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(111);
  }, 1000);
}).catch((reason) => {
  console.log(`[报错] reason=${reason}`);
  console.log(test) // status = pending
})

setTimeout(() => {
  console.log(test); //status = fulfulled
}, 3000)
  1. catch 函数会返回一个新的promise,而test就是这个新的promise
  2. catch 回调里,打印test的时候,整个回调函没有执行完成,所以此时的状态时pending
  3. catch回调函数里,如果成功执行了,没有报错,那么会改变整个新的Promise状态为fulfilled

生成器简介

特征:

  1. 命名:function* generator() {}

  2. 可中断,可恢复

    function* generator() {
      const list = [1,2,3];
      for (let i of list) {
        yield i;
      }
    }
    
    let g = generator();
    
    console.log(g.next()); // {value: 1, done: false}
    console.log(g.next()); // {value: 2, done: false}
    console.log(g.next()); // {value: 3, done: false}
    console.log(g.next()); // {value: undefined, done: true}  
    

async 简介

异步的操作同步书写

function longTimeFn(time) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(time);
    }, time)
  })
}

async function test() {
  console.log(new Date().toLocaleTimeString());
  await longTimeFn(1000);
  console.log(new Date().toLocaleTimeString());
}

test();