# 浅谈事件环与promise

213 阅读8分钟

1.事件环-宏任务和微任务

说到promise,还是得理清楚事件环中宏任务和微任务的关系。我们都知道js代码块是单进程的,代码执行顺序从上往下执行,其中的异步任务会添加到事件队列中,异步任务又分为宏任务和微任务ES6 规范中,宏任务(Macrotask) 称为 Task, 微任务(Microtask) 称为 Jobs。宏任务是由宿主(浏览器、Node)发起的,而微任务由 JS 自身发起。

1.2 常见的宏任务和微任务

宏任务:setTimeout,setInterval,script,MessageChannel,I/O,事件队列 微任务:Promise,process.nextTick(Node环境)

1.2 我所理解的事件环-Eventloop

本人对于事件环一开始也是懵懵懂懂,半知半解的,不搞懂吧,有总感觉心里刺挠,于是我拿着纸笔一顿乱写乱画,在那一瞬间似乎有种东西抑制不住的要喷涌而出,片刻后,爽~!于是就得到了下面这张图,朋友们,请看下图。

301626941427_.pic_hd.jpg 我们知道,js在执行完宏任务后(script),会去清空微任务队列。在script代码块执行的过程中,遇到宏任务就添加到宏任务的队列中,遇到微任务就添加到微任务的队列中。单单从这句话中,我个人还是难以理清任务间代码执行顺序的问题,既然如此,不妨大胆点,将宏任务和微任务加上等级标识,宏任务和微任务之间执行顺序的权重关系。上代码

<script>
    setTimeout(() => {
        console.log('setTimeout')
        Promise.resolve().then(() => {
            console.log('Promise2')
        })
    }, 0)
    Promise.resolve().then(() => {
        console.log('Promise1')
    }).then(res => {
        console.log('Promise1.2')
    })
    console.log('script')
</script>

我们将script代码块内的代码看成第一级宏任务队列,script代码执块行过程中,分为以下两步

  1. 执行宏任务队列中宏任务时,遇到宏任务添加到下一级宏任务队列中,遇到微任务添加到同级微任务队列中,当前宏任务执行完成后,如果异步队列中存在微任务,执行第二步,清空微任务队列,否之继续执行第一步,直至异步队列清空
  2. 在清空微任务队列时,遇到宏任务添加至下级宏任务队列,遇到微任务时,添加到同级微任务队列(当前微任务队列),清空微任务队列后,如果异步队列中存在宏任务,执行第一步

我们把上图中的每个方块看成了一个任务队列,实际上每次队列执行完后都会清空,为了形象的理解,增加了等级的概念,实际上还是同一个任务队列。

重点:每次执行完弘任务都要清空微任务队列。 总结:去掉等级概念后,代码在执行弘任务时,遇到弘任务添加到弘任务队列中,遇到微任务添加到微任务队列中,当前弘任务执行完成时,清空微任务队列,之行微任务时,遇到弘任务添加到弘任务队列中,遇到微任务添加到微任务队列中,微任务队列清空时,执行弘任务队列中的下一个弘任务,开始下一次循环。 精简:代码在执行异步任务时,遇到弘任务添加到弘任务队列中,遇到微任务添加到微任务队列中,每次执行完弘任务都要清空微任务队列,再执行下一个弘任务,开始下一次循环。

<script>
    setTimeout(() => {
        console.log('setTimeout')
        Promise.resolve().then(() => {
            console.log('Promise3')
        })
    }, 0)
    Promise.resolve().then(() => {
        console.log('Promise1')
        setTimeout(() => {
            console.log('setTimeout2')
        }, 0)
    }).then(() => {
        console.log('Promise2')
    })
    console.log('script')
</script>

上面这道题想必大家一看就知道正确答案了吧,接下来我们要步入今天的正题了——promise

2. promise分析

2.1 promise要点归集

  1. Promise 是一个类,在执行这个类的时候会传入一个执行器,这个执行器会立即执行
  2. Promise 会有三种状态
    • Pending 等待
    • Fulfilled 完成
    • Rejected 失败
  3. 状态只能由 Pending --> Fulfilled 或者 Pending --> Rejected,且一但发生改变便不可二次修改;
  4. Promise 中使用 resolve 和 reject 两个函数来更改状态;then 方法内部做但事情就是状态判断,如果状态是成功,调用成功回调函数,如果状态是失败,调用失败回调函数
  5. value-成功后向下传递的值,reason失败后向下传递的值
  6. promise状态未改变时,收集then上成功和失败回调
  7. then方法支持链式调用

2.2 promise类的实现

依照上面4点就可以新建一个promise类了

// 状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MyPromise {
  constructor(executor){
    // executor 是一个执行器,进入会立即执行-即promise执行器中的代码为同步代码
    // 并传入resolve和reject方法
    executor(this.resolve, this.reject)
  }

  // 储存状态的变量,初始值是 pending
  status = PENDING

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

  // 存储成功回调
  onFulfilledCallback = [];
  // 存储失败回调
  onRejectedCallback = [];


  // 更改成功后的状态
  resolve = (value) => {
    // 只有状态是等待,才执行状态修改
    if (this.status === PENDING) {
      // 状态修改为成功
      this.status = FULFILLED;
      // 保存成功之后的值
      this.value = value;
      // 清空存储的成功回调
      this.onFulfilledCallback.forEach(onFulfilled => onFulfilled(this.value))
      this.onFulfilledCallback = []
    }
  }

  // 更改失败后的状态
  reject = (reason) => {
    // 只有状态是等待,才执行状态修改
    if (this.status === PENDING) {
      // 状态成功为失败
      this.status = REJECTED;
      // 保存失败后的原因
      this.reason = reason;
      // 清空存储的失败回调
      this.onRejectedCallback.forEach(onRejected => onRejected(this.reason))
      this.onRejectedCallback = []
    }
  }
  // then方法
  then(onFulfilled, onRejected) {
    // 判断状态
    if (this.status === FULFILLED) {
        // 调用成功回调,并且把值返回
        onFulfilled(this.value);
    } else if (this.status === REJECTED) {
        // 调用失败回调,并且把原因返回
        onRejected(this.reason);
    } else {
        this.onFulfilledCallback.push(onFulfilled)
        this.onRejectedCallback.push(onRejected)
    }
  }
}
const promise = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
  }, 2000); 
})

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

2.3 then方法链式调用

  1. 链式调用即返回一个promise对象
  2. 通过return向下一个then传递参数
then(onFulfilled, onRejected) {
    // ==== 新增 ====
    // 为了链式调用这里直接创建一个 MyPromise,并在后面 return 出去
    const thenPromise = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        // 获取成功回调函数的执行结果
        const x = onFulfilled(this.value);
        // 传入 resolvePromise 集中处理
        resolvePromise(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 thenPromise;
}

// 判断x是不是 MyPromise 实例对象
function resolvePromise(x, resolve, reject) {
  if(x instanceof MyPromise) {
    // 执行 x,调用 then 方法,目的是将其状态变为 fulfilled 或者 rejected
    x.then(resolve, reject)
  } else{
    // 普通值
    resolve(x)
  }
}

避免循环引用,需要对then 方法返回的是自己的 Promise 对象的时候做处理。

resolvePromise(thenPromise, x, resolve, reject);

function resolvePromise(thenPromise, x, resolve, reject) {
  // 如果return的是自己,抛出类型错误并返回
  if(x === thenPromise) {
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
  }

  if(x instanceof MyPromise) {
    // 执行 x,调用 then 方法,目的是将其状态变为 fulfilled 或者 rejected
    x.then(resolve, reject)
  } else{
    // 普通值
    resolve(x)
  }
}

这个时候then中代码的执行,必须依赖(thenPromise完成初始化,我们使用queueMicrotask创建一个异步函数去等待 promise2 完成初始化

then(onFulfilled, onRejected) {
  ...
  if (this.status === FULFILLED) {
    queueMicrotask(() => {
      const x = onFulfilled(this.value);
      resolvePromise(promise2, x, resolve, reject);
    })
  }
  ...
}

catch方法

catch方法可以看成then的语法糖

catch(onRejected) {
  this.then(null, onRejected)
}

改变then的传参,使其变得不必须,并且,如果then没有返回值,上次的结果将透传

then(onFulfilled, onRejected) {
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
  onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason};
  ...
}

添加resolve、reject返回promise对象

MyPromise {
  ......
  static resolve (parameter) {
    // 如果传入 MyPromise 就直接返回
    return value instanceof Promise ? value : new Promise((resolve) => resolve(value));
  }

  static reject (reason) {
    return new MyPromise((resolve, reject) => {
      reject(reason);
    });
  }
}

全部代码

class MyPromise {
  constructor(executor){
    // executor 是一个执行器,进入会立即执行-即promise执行器中的代码为同步代码
    // 并传入resolve和reject方法
    executor(this.resolve, this.reject)
  }

  // 储存状态的变量,初始值是 pending
  status = PENDING

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

  // 存储成功回调
  onFulfilledCallback = [];
  // 存储失败回调
  onRejectedCallback = [];


  // 更改成功后的状态
  resolve = (value) => {
    // 只有状态是等待,才执行状态修改
    if (this.status === PENDING) {
      // 状态修改为成功
      this.status = FULFILLED;
      // 保存成功之后的值
      this.value = value;
      // 清空存储的成功回调
      this.onFulfilledCallback.forEach(onFulfilled => onFulfilled(this.value))
      this.onFulfilledCallback = []
    }
  }

  // 更改失败后的状态
  reject = (reason) => {
    // 只有状态是等待,才执行状态修改
    if (this.status === PENDING) {
      // 状态成功为失败
      this.status = REJECTED;
      // 保存失败后的原因
      this.reason = reason;
      // 清空存储的失败回调
      this.onRejectedCallback.forEach(onRejected => onRejected(this.reason))
      this.onRejectedCallback = []
    }
  }
  // then方法
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason};
    const thenPromise = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        // 获取成功回调函数的执行结果
        const x = onFulfilled(this.value);
        // 传入 resolvePromise 集中处理
        resolvePromise(thenPromise, 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 thenPromise;
  }
  catch(onRejected) {
    this.then(null, onRejected)
  }

  static resolve (parameter) {
    // 如果传入 MyPromise 就直接返回
    return value instanceof Promise ? value : new Promise((resolve) => resolve(value));
  }

  static reject (reason) {
    return new MyPromise((resolve, reject) => {
      reject(reason);
    });
  }
}

function resolvePromise(thenPromise, x, resolve, reject) {
  // 如果return的是自己,抛出类型错误并返回
  if(x === thenPromise) {
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
  }

  if(x instanceof MyPromise) {
    // 执行 x,调用 then 方法,目的是将其状态变为 fulfilled 或者 rejected
    x.then(resolve, reject)
  } else{
    // 普通值
    resolve(x)
  }
}

promise A+

finally

无论promise成功或失败,finally方法都会执行接收到的回调函数,并返回一个promise实例

finally(callback) {
  this.then(callback, callback)
}

race

无论promise成功或失败,finally方法都会执行接收到的回调函数,并返回一个promise实例

race(promiseArr) {
  return new MyPromise((resolve, reject) => {
    promiseArr.forEach((promise) => {
      MyPromise.resolve(promise).then(resolve, reject)
    })
  })
}

all

利用与入参数组长度相同的结果数组以索引映射方式去接收结果集,没成功接收一个结果,计数器+1,当达到入参数组长度时,表示所有异步操作都成功,返回结果

all(promiseArr) {
  return new MyPromise((resolve, reject) => {
    // 如果Promise.all接收到的是一个空数组([]),它会立即返回结果。
    if (!promiseArr.length) {
      resolve([])
    }
    length = promiseArr.length
    let result = new Array(length).fill(null)
    let resolvedPro = 0
    
    for (let index = 0; index < length; index++) {
      MyPromise.resolve(promiseArr[index]).then(
        (data) => {
          // 注意,这里要用index赋值,而不是push。因为要保持返回值和接收到的promise的位置一致性。
          result[index] = data
          if (++resolvedPro === length) {
            resolve(result)
          }
        },
        (error) => {
          reject(error)
        }
      )
    }
  })
}

allSettled

与all同理,只是失败的时候依旧执行resolve向下传递

allSettled(promiseArr) {
  return new MyPromise((resolve, reject) => {
    if (!promises.length) {
      resolve([]);
    }
    length = promiseArr.length
    let result = new Array(length).fill(null)
    let resolvedPro = 0
    for (let index = 0; index < length; index++) {
      myPromise.resolve(promiseArr[index])
        .then((data) => {
          // 注意,这里要用index赋值,而不是push。因为要保持返回值和接收到的promise的位置一致性。
          result[index] = {
            status: FULFILLED_STATE,
            value: data,
          };
          if (++resolvedPro === length) {
            resolve(result);
          }
        })
        .catch((error) => {
          result[index] = {
            status: REJECTED_STATE,
            reason: error,
          };
          if (++resolvedPro === length) {
            resolve(result)
          }
        })
    }
  })
}