Skip to content

js 异步

EventLoop

如何理解 JS 的异步

JS 运行在浏览器的渲染主线程中,而渲染主线程只有一个。它承担着很多工作,比如渲染页面、执行 JS 等。如果它采用同步的方式,那么执行类似于网络请求的任务时会一直等待响应,那就很有可能导致消息队列中的其他任务无法得到执行。这样的阻塞就会造成页面无法及时更新而卡死。 所以浏览器采用异步的方式,当某些任务发生时,比如计时器、网络、事件监听,主线程将任务交给其他线程去处理,自身立即结束任务的执行,转而执行后续代码。当其它线程完成时,将事先传递的回调函数包装成任务,加入到消息队列的末尾排队,等待主线程调度执行。 在这种异步模式下,浏览器永不阻塞,从而最大限度的保证了单线程的流畅运行。

阐述一下 JS 的事件循环

事件循环又叫做消息循环,是浏览器渲染主线程的工作方式。 在 Chrome 中,它开启一个不会结束的 for 循环,每次循环从消息队列中取出第一个任务执行,而其他线程只需要在合适的时候将任务加入到队列末尾即可。消息队列有多个,在一次事件循环中,由浏览器自行决定取哪一个队列的任务,微队列优先级最高,其次交互队列,然后是延迟队列。当然队列还有很多,这三个是最常见的。

JS 中的计时器能做到精确计时吗

不行,因为

  • 计算机硬件没有原子钟,无法做到精确计时
  • 操作系统的计时函数本身就有少量偏差,由于 JS 的计时器最终调用的是操作系统的函数,也就是携带了这些偏差。
  • 浏览器实现计时器时,如果嵌套层级超过 5 层,则会带有 4 毫秒的最少时间,这样在计时时间少于 4 毫秒时又带来了偏差。
  • 受事件循环的影响,计时器的回调函数会放到延迟队列中,延迟队列的优先级比较低,因此又带来了偏差。

EventLoop 相关示例代码输出

html
<html>
  <head>
    <script>
      (function test() {
        setTimeout(() => {
          console.log(1);
        }, 0);
        new Promise((resolve) => {
          console.log(2);
          for (var i = 0; i < 10000; i++) {
            i == 9999 && resolve(3);
          }
          console.log(4);
        }).then((data) => {
          console.log(data);
        });
        console.log(5);
      })();
    </script>
    <script>
      (function test2() {
        setTimeout(() => {
          console.log(6);
        }, 0);
        new Promise((resolve) => {
          console.log(7);
          for (var i = 0; i < 10000; i++) {
            i == 9999 && resolve(8);
          }
          console.log(9);
        }).then((data) => {
          console.log(data);
        });
        console.log(10);
      })();
    </script>
  </head>
  <body></body>
  <html></html>
</html>
<!-- 2 4 5 3 7 9 10 8 1 6 -->

Promise

Promise 有哪几种状态

  • pending: 初始状态, 非 fulfilled 或 rejected.
  • fulfilled: 成功的操作.
  • rejected: 失败的操作.

then 和 catch 的处理

then 接收两个函数,第一个函数处理 fulfilled,第二个函数处理 rejected,它们正常处理完会 return 一个 fulfilled 状态的 Promise,但如果执行报错或手动throw new Error,那 return 一个 rejected 状态的 Promise;新的 Promise 会被后面的 then 或 catch 对应接收处理。

catch 接收一个函数,处理 rejected,它正常处理完是 return 一个 fulfilled 状态的 Promise,但如果执行报错或手动throw new Error,那 return 一个 rejected 状态的 Promise。

js
Promise.resolve()
  .then(() => {
    console.log(1);
  })
  .catch(() => {
    console.log(2);
  })
  .then(() => {
    console.log(3);
  });
// 打印1和3
js
Promise.resolve()
  .then(() => {
    console.log(1);
    throw new Error("error");
  })
  .catch(() => {
    console.log(2);
  })
  .then(() => {
    console.log(3);
  });
// 打印1和2和3
js
Promise.resolve()
  .then(() => {
    console.log(1);
    throw new Error("error");
  })
  .catch(() => {
    console.log(2);
  })
  .catch(() => {
    console.log(3);
  });
// 打印1和2
js
Promise.reject()
  .then(() => {
    console.log(1);
    throw new Error("error");
  })
  .catch(() => {
    console.log(2);
    throw new Error("error2");
  })
  .then(
    () => {
      console.log(3);
    },
    () => {
      console.log(4);
    }
  )
  .catch(() => {
    console.log(5);
  });
// 打印2和4

async-await 和 Promise 的关系

  • 执行 async 函数,返回的是一个 Promise 对象
  • await 相当于 Promise 的 then 的第一个函数
  • try-catch 相当于 Promise 的 catch

手写 Promise

第一步:

js
/**
 * 1.构造基础的Promise;异步完成通知改状态存结果;捕获意外错误。
 */

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

class MyPromise {
  _state = PENDING;
  _value = undefined;
  // Promise的构造函数入参是一个函数
  constructor(executor) {
    try {
      // 该函数立即执行的;异步操作返回后,会用resolve或reject通知Promise
      executor(this._resolve.bind(this), this._reject.bind(this));
    } catch (error) {
      // executor执行可能会报错,例如用户写错语法,那我们自动帮它调用reject
      this._reject(error);
    }
  }
  // 异步成功,通知Promise改状态为fulfilled,记录异步结果
  _resolve(data) {
    this._changeState(FULFILLED, data);
  }
  // 异步异常,通知Promise改状态为rejected,记录异常原因
  _reject(reason) {
    this._changeState(REJECTED, reason);
  }
  // Promise改变状态时,只能由pending变为fulfilled或rejected
  _changeState(state, data) {
    if (this._state !== PENDING) return;
    this._state = state;
    this._value = data;
  }
}
const p = new MyPromise((resolve, reject) => {
  reject(1);
});
console.log(p);
/* new Promise((resolve, reject) => {}) */

第二步:

js
/**
 * 1.构造基础的Promise;异步完成通知改状态存结果;捕获意外错误。
 *
 * 2.then会注册回调函数并返回新Promise;构造队列存储多个回调函数;新Promise的状态由谁决定。
 */

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

class MyPromise {
  _state = PENDING;
  _value = undefined;
  /* 
    同一个Promise对象可以多次调用then,它就会注册很多回调函数,那么就需要将这么多回调函数
    放到队列中等待处理
  */
  _handlers = [];
  constructor(executor) {
    try {
      executor(this._resolve.bind(this), this._reject.bind(this));
    } catch (error) {
      this._reject(error);
    }
  }
  /* 
    同一个Promise对象调用一次then,会注册两个回调函数,第一个回调函数是处理fulfilled的结
    果,第二个回调函数处理rejected的结果,then最终会返回一个新的Promise对象
  */
  then(onResolved, onRejected) {
    /* 
      新Promise对象的状态是由回调函数执行情况决定的,一般的,回调函数正常执行完那就调用
      新Promise对象的resolve,代码执行报错就会调用新Promise对象的reject
    */
    return new MyPromise((resolve, reject) => {
      this._pushHandler(onResolved, FULFILLED, resolve, reject);
      this._pushHandler(onRejected, REJECTED, resolve, reject);
    });
  }
  _resolve(data) {
    this._changeState(FULFILLED, data);
  }
  _reject(reason) {
    this._changeState(REJECTED, reason);
  }
  _changeState(state, data) {
    if (this._state !== PENDING) return;
    this._state = state;
    this._value = data;
  }
  _pushHandler(executor, state, resolve, reject) {
    this._handlers.push({ executor, state, resolve, reject });
  }
}
const p = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(1);
  }, 1000);
});
p.then(
  function A1() {},
  function A2() {}
);
p.then(
  function B1() {},
  function B2() {}
);
console.log(p);

第三步:

js
/**
 * 1.构造基础的Promise;异步完成通知改状态存结果;捕获意外错误。
 *
 * 2.then会注册回调函数并返回新Promise;构造队列存储多个回调函数;新Promise的状态由谁决定。
 *
 * 3.回调函数队列执行时机有2个;每次出队列一个执行对象进行处理。
 */

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

class MyPromise {
  _state = PENDING;
  _value = undefined;
  _handlers = [];
  constructor(executor) {
    try {
      executor(this._resolve.bind(this), this._reject.bind(this));
    } catch (error) {
      this._reject(error);
    }
  }
  then(onResolved, onRejected) {
    return new MyPromise((resolve, reject) => {
      this._pushHandler(onResolved, FULFILLED, resolve, reject);
      this._pushHandler(onRejected, REJECTED, resolve, reject);
      /*
        如果Promise里没有异步直接就resolve或reject了,那么此时then会比改变状态慢一步。
        这就导致_changeState里的_runHandlers先执行了,但那时候还没有注册回调函数,那么
        就需要在此处再调用一次_runHandlers,确保包含上述场景
      */
      this._runHandlers();
    });
  }
  _resolve(data) {
    this._changeState(FULFILLED, data);
  }
  _reject(reason) {
    this._changeState(REJECTED, reason);
  }
  _changeState(state, data) {
    if (this._state !== PENDING) return;
    this._state = state;
    this._value = data;
    // 当Promise状态改变后,就会尝试去执行对应的回调函数(先注册回调函数后改变状态)
    this._runHandlers();
  }
  _pushHandler(executor, state, resolve, reject) {
    this._handlers.push({ executor, state, resolve, reject });
  }
  _runHandlers() {
    // 要去处理回调了,但此时还是pending状态那就不处理(注册回调时,可能还在异步还是pending)
    if (this._state === PENDING) return;
    console.log(`处理了${this._handlers.length}个函数`);
    console.log("this._handlers", this._handlers);
    // 每次从开头取一个出来处理,处理完后删除
    while (this._handlers[0]) {
      const handler = this._handlers[0];
      this._runOneHandler(handler);
      this._handlers.shift();
    }
  }
  _runOneHandler() {}
}
const p = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(1);
  });
});
p.then(function A1() {});
setTimeout(() => {
  p.then(function A2() {});
});

第四步:

js
// 放入微队列
function myQueueMicroTask(task) {
  if (process && process.nextTick) {
    process.nextTick(task);
  } else if (MutationObserver) {
    const p = document.createElement("p");
    const observer = new MutationObserver(task);
    observer.observe(p, { childList: true });
    p.innerHTML = "test";
  } else if (queueMicrotask) {
    queueMicrotask(task);
  } else {
    setTimeout(task);
  }
}
js
/**
 * 1.构造基础的Promise;异步完成通知改状态存结果;捕获意外错误。
 *
 * 2.then会注册回调函数并返回新Promise;构造队列存储多个回调函数;新Promise的状态由谁决定。
 *
 * 3.回调函数队列执行时机有2个;每次出队列一个执行对象进行处理。
 *
 * 4.根据回调函数类型决定是直接执行还是状态穿透,函数返回本身是Promise那就用它的then来处理。
 */

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

class MyPromise {
  _state = PENDING;
  _value = undefined;
  _handlers = [];
  constructor(executor) {
    try {
      executor(this._resolve.bind(this), this._reject.bind(this));
    } catch (error) {
      this._reject(error);
    }
  }
  then(onResolved, onRejected) {
    return new MyPromise((resolve, reject) => {
      this._pushHandler(onResolved, FULFILLED, resolve, reject);
      this._pushHandler(onRejected, REJECTED, resolve, reject);
      this._runHandlers();
    });
  }
  _resolve(data) {
    this._changeState(FULFILLED, data);
  }
  _reject(reason) {
    this._changeState(REJECTED, reason);
  }
  _changeState(state, data) {
    if (this._state !== PENDING) return;
    this._state = state;
    this._value = data;
    this._runHandlers();
  }
  _pushHandler(executor, state, resolve, reject) {
    this._handlers.push({ executor, state, resolve, reject });
  }
  _runHandlers() {
    if (this._state === PENDING) return;
    while (this._handlers[0]) {
      const handler = this._handlers[0];
      this._runOneHandler(handler);
      this._handlers.shift();
    }
  }
  _runOneHandler({ executor, state, resolve, reject }) {
    queueMicrotask(() => {
      // 状态匹配上才执行
      if (state !== this._state) return;
      // 如果不是函数,那么状态穿透
      if (typeof executor !== "function") {
        // 意思是then的Promise状态和结果,会作为then返回新Promise的状态和结果
        this._state === FULFILLED ? resolve(this._value) : reject(this._value);
        return;
      }
      try {
        // 是函数,还要分执行结果是普通值还是Promise
        const rlt = executor(this._value);
        // 是Promise,那么这个Promise的状态和结果会决定then返回新Promise的状态和结果
        if (isPromise(rlt)) {
          rlt.then(resolve, reject);
        } else {
          resolve(rlt);
        }
      } catch (error) {
        // 函数执行报错那就直接reject
        reject(error);
      }
    });
  }
}
// 是对象并且具有属性then,这个then属性是一个函数,那么这个对象就是Promise对象
function isPromise(param) {
  return !!(param && typeof param === "object" && typeof param.then === "function");
}
const p1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(1);
  });
});
const p2 = p1.then((data) => {
  console.log(data);
  return new MyPromise((resolve, reject) => {
    resolve("a");
  });
});
setTimeout(() => {
  console.log(p2);
}, 50);

完整的 PromiseA+

js
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

// 放入微队列
const myQueueMicroTask = (task) => {
  if (process && process.nextTick) {
    // node
    process.nextTick(task);
  } else if (MutationObserver) {
    // 浏览器
    const p = document.createElement("p");
    const observer = new MutationObserver(task);
    observer.observe(p, { childList: true });
    p.innerHTML = "test";
  } else if (queueMicrotask) {
    // 新API
    queueMicrotask(task);
  } else {
    // 最差的情况
    setTimeout(task);
  }
};
/**
 * 判断入参是否符合Promise形式,规则:param是普通对象或者函数,不能是基础类型,它有一个
 * then属性,该属性得是一个函数
 * @param {*} param 
 * @returns then或者null
 *  这个thenable有一种特殊形式,param.then抛错,此时调用thenable()的地方就应该try-catch处理
 *  Object.create(null, {
        then: {
            get: function () {
                throw reason;
            }
        }
    });
 */
const thenable = (param) => {
  if ((typeof param === "object" && param != null) || typeof param === "function") {
    // 单独取then,因为要过promises-aplus-tests的2.3.3.1检测,即只执行一次“param.then”
    const then = param.then;
    if (typeof then === "function") {
      return then;
    }
  }
  return null;
};

class Mypromise {
  _state = PENDING;
  _value = undefined;
  _reason = undefined;
  /* 
    同一个Promise对象可以多次调用then,它就会注册很多回调函数,那么就需要将这么多回调函数
    放到队列中等待处理
  */
  _handlers = [];
  // Promise的构造函数入参是一个函数
  constructor(executor) {
    try {
      // executor立即执行的;executor内部的异步操作完成后,会用resolve或reject通知Promise
      executor(this._resolve, this._reject);
    } catch (error) {
      // executor执行可能会报错,例如用户写错语法,那我们自动帮它调用reject
      this._reject(error);
    }
  }
  /* 
    同一个Promise对象调用一次then,会注册两个回调函数,第一个回调函数是处理fulfilled的结
    果,第二个回调函数处理rejected的结果,then最终会返回一个新的Promise对象
  */
  then(onResolved, onRejected) {
    /* 
      新Promise对象的状态是由回调函数执行情况决定的,一般的,回调函数正常执行完那就调用
      新Promise对象的resolve,代码执行报错就会调用新Promise对象的reject
    */
    let res, rej;
    const promise = new Mypromise((resolve, reject) => {
      // 不能将_pushHandler放这里,原因是新promise没有完全生成,所以得放到new Mypromise后
      res = resolve;
      rej = reject;
    });
    // onResolved和onRejected返回的对象不能是这个新promise,所以要将promise传进去
    this._pushHandler(FULFILLED, onResolved, res, rej, promise);
    this._pushHandler(REJECTED, onRejected, res, rej, promise);
    /*
      一般情况下,then会比异步先执行,也就是回调函数先注册好了,等待异步执行完就可以
      调用回调函数了,这完全没有问题。(先注册回调函数,后执行回调函数)
      但是有一种情况,就是executor内没有异步操作,直接resolve或reject了,然后执行链式
      调用then,这个时候才注册好回调函数。(先执行回调函数,后才注册回调函数,行不通)
      解决:在链式调用then注册好回调函数,就尝试执行一次回调函数,如果state是pending
      状态时就自动跳过执行。(注册回调函数,遇到pending跳过执行,等待状态改变再执行)
    */
    this._runHandlers(); // push后,也就是注册好回调函数,此时就尝试执行回调函数
    return promise;
  }
  // 异步成功,通知Promise改状态为fulfilled,记录异步结果
  _resolve = (value) => {
    this._changeState(FULFILLED, value);
  };
  // 异步异常,通知Promise改状态为rejected,记录异常原因
  _reject = (reason) => {
    this._changeState(REJECTED, reason);
  };
  _changeState(state, data) {
    // Promise改变状态时,只能由pending变为fulfilled或rejected
    if (this._state !== PENDING) return;
    this._state = state;
    state === FULFILLED ? (this._value = data) : (this._reason = data);
    // 当Promise状态改变后,就会尝试去执行对应的回调函数(先注册回调函数,后执行回调函数)
    this._runHandlers();
  }
  _pushHandler(state, executor, resolve, reject, promise) {
    this._handlers.push({ state, executor, resolve, reject, promise });
  }
  _runHandlers() {
    // 要去处理回调了,但此时还是pending状态那就不处理(注册回调时,可能还在异步还是pending)
    if (this._state === PENDING) return;
    // 每次从开头取一个出来处理,处理完后删除
    while (this._handlers[0]) {
      const handler = this._handlers[0];
      this._runOneHandler(handler);
      this._handlers.shift();
    }
  }
  _runOneHandler({ state, executor, resolve, reject, promise }) {
    // 放入微队列执行
    myQueueMicroTask(() => {
      // 状态匹配上才执行,此时状态只会是fulfilled或者rejected,只执行对应的回调
      if (state !== this._state) return;
      // 如果不是函数,那么状态穿透,也就是将本Promise对象的状态直接交给新Promise对象
      if (typeof executor !== "function") {
        // 意思是then前的Promise状态和结果,会作为then后新的Promise的状态和结果
        this._state === FULFILLED ? resolve(this._value) : reject(this._reason);
        return;
      }
      try {
        // 是函数,还要分执行结果是普通值还是Promise
        const rlt = executor(this._state === FULFILLED ? this._value : this._reason);
        this._handleExecutRlt(rlt, resolve, reject, promise);
      } catch (error) {
        // 函数执行报错那就直接reject
        reject(error);
      }
    });
  }
  _handleExecutRlt(rlt, resolve, reject, promise) {
    // executor执行返回的结果不能是then返回的结果 Promise/A+ 2.3.1
    if (rlt === promise) return reject(new TypeError("Promise循环引用了,Promise/A+ 2.3.1"));

    let called;
    try {
      const then = thenable(rlt);
      /* 
        判断rlt是否是一个Promise形式,是的话就执行它的then,用then的执行情况决定是resolve
        还是reject。then执行后又是一个Promise形式,就继续解析,直到它是一个普通值。
      */
      if (then) {
        then.call(
          rlt,
          (val) => {
            // Promise/A+ 2.3.3.3.3 只能调用一次
            if (called) return;
            called = true;

            // 如果val还是一个Promise形式的,那么就递归处理它,直到是普通值
            this._handleExecutRlt(val, resolve, reject, promise);
          },
          (r) => {
            // Promise/A+ 2.3.3.3.3 只能调用一次
            if (called) return;
            called = true;

            reject(r);
          }
        );
      } else {
        // 是普通值,将这个执行结果包成新Promise供后面的人进行链式调用
        resolve(rlt);
      }
    } catch (error) {
      // Promise/A+ 2.3.3.3.3 只能调用一次
      if (called) return;
      called = true;

      reject(error);
    }
  }
}
module.exports = Mypromise;

手写 Promise 的 catch 和 finally

js
class MyPromise {
  catch(onRejected) {
    let res, rej;
    const promise = new MyPromise((resolve, reject) => {
      (res = resolve), (rej = reject);
    });
    this._pushHandler(REJECTED, onRejected, res, rej, promise);
    this._runHandlers();
    return promise;
  }
}
js
class MyPromise {
  finally(onFinished) {
    return this.then(
      (value) => {
        onFinished(); // 执行回调,接收和返回的数据都不做处理
        return value;
      },
      (reason) => {
        onFinished(); // 执行回调,接收和返回的数据都不做处理
        throw reason;
      }
    );
  }
}

手写 Promise.all 和 race

js
class MyPromise {
  // 等到所有的 promise 对象都成功或有任意一个 promise 失败。
  static all(params) {
    return new MyPromise((resolve, reject) => {
      try {
        let count = 0;
        let fulfilledCount = 0;
        const rlt = [];
        for (const p of params) {
          const i = count;
          count++;
          MyPromise.resolve(p).then((val) => {
            rlt[i];
            if (++fulfilledCount === count) {
              resolve(rlt);
            }
          }, reject);
        }
        if (!count) resolve([]);
      } catch (error) {
        reject(error);
      }
    });
  }
}
js
class MyPromise {
  // 等到任意一个 promise 的状态变为已敲定。
  static race(params) {
    return new MyPromise((resolve, reject) => {
      try {
        for (const p of params) {
          MyPromise.resolve(p).then(resolve, reject);
        }
        // 如果是[],就永远pending
      } catch (error) {
        reject(error);
      }
    });
  }
}

手写 Promise.allSettled 和 any

js
class MyPromise {
  // 等到所有 promise 都已敲定(每个 promise 都已兑现或已拒绝)
  static allSettled(params) {
    return new MyPromise((resolve, reject) => {
      try {
        let count = 0;
        let settledCount = 0;
        const rlt = [];
        for (const p of params) {
          const i = count;
          count++;
          MyPromise.resolve(p).then(
            (value) => {
              rlt[i] = { state: FULFILLED, value };
              if (++settledCount === count) {
                resolve(rlt);
              }
            },
            (reason) => {
              rlt[i] = { state: REJECTED, reason };
              if (++settledCount === count) {
                resolve(rlt);
              }
            }
          );
        }
        if (!count) resolve([]);
      } catch (error) {
        reject(error);
      }
    });
  }
}
js
class MyPromise {
  // 任意一个 promise 变成了兑现状态那就返回已兑现的promise,
  // 最终都没有兑现,那就返回拒绝的promise
  static any(params) {
    return new MyPromise((resolve, reject) => {
      try {
        let count = 0;
        let handleCount = 0;
        const errors = [];
        for (const p of params) {
          const i = count;
          count++;
          MyPromise.resolve(p).then(resolve, () => {
            errors[i] = new Error("xxx");
            if (++handleCount === count) {
              reject(
                new AggregateError({
                  name: "AggregateError",
                  message: "All Promises rejected",
                  errors,
                })
              );
            }
          });
        }
        if (!count) {
          reject(
            new AggregateError({
              name: "AggregateError",
              message: "params is empty",
              errors: [],
            })
          );
        }
      } catch (error) {
        reject(error);
      }
    });
  }
}

MIT Licensed | Copyright © 2023-present LiawnLiu