Skip to content

说说 JS 异步发展史

查看详情
  • 回调函数
    • 回调地狱
    • 不能使用 try catch 捕获错误
    • 嵌套函数存在耦合性,一旦有所改动,就会牵一发而动全身
    • 最早解决了同步的问题
  • Promise 实现了链式调用
    • 解决了回调地狱的问题
    • 无法取消 Promise 状态
    • 错误需要通过回调函数来捕获
  • Generator
    • 可以控制函数的执行
  • async/await
    • 代码清晰,处理了回调地狱的问题
    • 如果多个异步操作没有依赖性使用 await 会导致性能上的降低,建议一同触发

常见的异步任务

查看详情
  • setTimeout/setInterval
  • ajax
  • 回调函数
  • async await
  • promise

promise 有几种状态,promise 有什么优缺点?

查看详情
  • 三种状态:
    • pending
    • fulfilled
    • rejected
  • 优点:
    • 一旦状态改变就不能在变,任何时候都可以得到这个结果
    • 可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数
  • 缺点:
    • 无法取消 promise
    • 当处于 pending 状态时,无法得知目前进行到哪一阶段

promise 构造函数是同步执行的还是异步执行的?then 中的方法呢?

查看详情
  • promise 的构造函数是同步执行的
  • then 中的方法是异步执行的

谈谈事件循环(EventLoop)

查看详情
  • JavaScript 是一门单线程的脚本语言,他的异步是通过 EventLoop 来实现的,分为三个部分:调用栈,微任务队列和宏任务队列
  • 事件循环的工作流程如下:
    • 执行全局代码:首先,JavaScript 解析并执行全局代码,将所有同步任务放入调用栈。
    • 调用栈为空时:一旦调用栈为空(即所有同步任务执行完毕),事件循环会检查微任务队列是否为空。
    • 执行微任务:如果微任务队列不为空,事件循环会依次执行所有微任务,直到微任务队列为空。
    • 执行宏任务:当微任务队列为空后,事件循环会从宏任务队列中取出第一个任务并执行其回调函数。
    • 重复上述步骤:事件循环不断重复上述步骤,直到程序终止。

常见的宏任务和微任务

查看详情
  • 宏任务
    • setTimeout
    • setInterval
    • requestAnimationFrame
  • 微任务
    • promise.then
    • promise.catch
    • promise.finally
    • await 后的代码(async 方法中 await 后跟的方法以及它们前的代码都是同步执行的,但由于 await 隐式返回 promise,它后边的代码类似于 promise.then 中的代码,属于微任务)
javascript
async function async1() {
  console.log(1);
  const result = await async2();
  console.log(3);
}
async function async2() {
  console.log(2);
}
Promise.resolve().then(() => {
  console.log(4);
});
setTimeout(() => {
  console.log(5);
});
async1();
console.log(6);
// 答案:1 2 6 4 3 5

如何实现 Promise.all

查看详情
  • Promise.all 的功能:将多个 Promise 实例包装成一个新的 Promise 实例, p = Promise.all([p1, p2, p3])
    • 只有 p1, p2, p3 状态都变为 fulfilled,p 的状态才会变为 fulfilled,此时 p1, p2, p3 的返回值组成一个数组传递给回调函数
    • 只要 p1, p2, p3 之中一个被 rejected,p 的状态就变为 rejected,此时第一个被 reject 的实例的返回值会传给 p 的回调函数
javascript
Promise.all = function (promises) {
  return new Promise((resolve, reject) => {
    let index = 0;
    let result = [];
    if (promises.length === 0) {
      resolve(result);
    } else {
      function processValue(i, data) {
        result[i] = data;
        if (++index === promises.length) {
          resolve(result);
        }
      }
      for (let i = 0; i < promises.length; i++) {
        //promises[i] 可能是普通值
        Promise.resolve(promises[i]).then(
          (data) => {
            processValue(i, data);
          },
          (err) => {
            reject(err);
            return;
          }
        );
      }
    }
  });
};

如何实现 Promise.race?

查看详情
  • 同样是将多个 Promise 实例包装成一个新的 Promise 实例。不同的是只要有一个先改变状态,新 Promise 的状态就会跟着改变。
    • 如果传入的参数是不可迭代的将抛出错误
    • 如果传入的参数数组是空,那么返回的 promise 将永远等待
javascript
Promise.race = function (promises) {
  //promises 必须是一个可遍历的数据结构,否则抛错
  return new Promise((resolve, reject) => {
    if (typeof promises[Symbol.iterator] !== 'function') {
      //真实不是这个错误
      Promise.reject('args is not iteratable!');
    }
    if (promises.length === 0) {
      return;
    } else {
      for (let i = 0; i < promises.length; i++) {
        Promise.resolve(promises[i]).then(
          (data) => {
            resolve(data);
            return;
          },
          (err) => {
            reject(err);
            return;
          }
        );
      }
    }
  });
};

如何实现 Promise.finally

查看详情
  • 不管 Promise 对象最后的状态如何都会执行。
javascript
Promise.prototype.finally = function (callback) {
  return this.then(
    (value) => {
      return Promise.resolve(callback()).then(() => {
        return value;
      });
    },
    (err) => {
      return Promise.resolve(callback()).then(() => {
        throw err;
      });
    }
  );
};

谈谈对 async/await 的理解

查看详情
  • async/await 是基于 Promise 实现的,与 Promise 一样是非阻塞的
  • async/await 使得异步代码看起来像同步代码一样
  • async 函数返回一个 Promise 对象,可以用 then 指定下一步操作
  • async 函数内部抛出的错误会导致返回的 Promise 对象变为 reject 状态,错误对象会被 catch 方法接收到
  • async 函数返回的 Promise 对象必须等到内部所有的 await 命令后面的 Promise 对象执行完才会发生状态改变,除非遇见 return 语句或者抛出错误
  • async 函数就是 Generator 函数的语法糖,它内置执行器,与普通函数一样;比起星号和 yield 来说,async/await 更加语义化
  • async 函数的 await 命令后面可以是 Promise 对象和原始类型的值,适用性更广;如果不是 Promise 对象,会被转成一个立即 resolve 的 Promise 对象。

async 实现的原理是什么?

查看详情
  • async 函数的实现原理就是将 Generator 函数和自动执行器,包装在一个函数里。
javascript
async function fn(args) {
  const res = await XX;
}
// 等同于(spawn函数就是自动执行器)
function fn(args) {
  return spawn(function* () {
    const res = yield XX;
  });
}

使用 async/await 需要注意什么?

查看详情
  • 最好把 await 命令放在 try...catch 代码块中
  • 多个 await 命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。
// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
  • await 命令只能用在 async 函数之中,如果用在普通函数,就会报错