JavaScript内核与高级编程之:`Promise`的`then`方法:其微任务队列的调度与链式调用。

各位观众老爷们,早上好/下午好/晚上好!今天咱们来聊聊JavaScript里一个挺有意思的东西——Promisethen方法,以及它背后的微任务队列,还有链式调用这些事儿。别担心,我会尽量说得轻松点儿,争取让大家听完之后,感觉就像刚吃完一顿火锅,浑身舒坦!

开场白:Promise这玩意儿到底是个啥?

在正式开始之前,咱们先简单回顾一下Promise。你可以把它想象成一张欠条,你让别人帮你办件事儿,办成了给你个糖,没办成给你个巴掌。这个“糖”就是resolve,表示成功;“巴掌”就是reject,表示失败。而Promise本身,就是这张欠条。

then方法:承上启下,连接美好未来

好了,有了Promise,我们怎么知道事情办成了没?这时候就轮到咱们今天的主角——then方法登场了。then方法就像一个中间人,它会告诉你,欠条上的事情办成了还是没办成,然后根据结果来决定下一步该怎么走。

then方法接收两个参数(都是可选的):

  • onFulfilled: 当Promise状态变为fulfilled(成功)时调用的回调函数。
  • onRejected: 当Promise状态变为rejected(失败)时调用的回调函数。

简单来说,then就像一个条件判断,如果Promise成功了,就执行onFulfilled,否则执行onRejected

代码示例:then的基本用法

const promise = new Promise((resolve, reject) => {
  // 模拟异步操作
  setTimeout(() => {
    const randomNumber = Math.random();
    if (randomNumber > 0.5) {
      resolve("事情办成了!randomNumber: " + randomNumber); // 成功
    } else {
      reject("事情没办成!randomNumber: " + randomNumber); // 失败
    }
  }, 1000);
});

promise.then(
  (result) => {
    console.log("成功了:", result);
  },
  (error) => {
    console.error("失败了:", error);
  }
);

console.log("`Promise`创建之后,`then`之前");

运行这段代码,你会发现控制台会先输出“Promise创建之后,then之前”,然后过一秒钟左右,才会输出成功或失败的结果。这是因为setTimeout是异步操作,而then方法的回调函数会被放入微任务队列中,等待主线程空闲时执行。

微任务队列:then背后的秘密

现在,我们来聊聊微任务队列。这玩意儿是JavaScript事件循环机制中非常重要的一部分。简单来说,它是一个存放微任务的队列,微任务会在当前宏任务执行完毕后,但在浏览器渲染之前执行。

常见的微任务包括:

  • Promisethencatchfinally回调
  • MutationObserver的回调
  • queueMicrotask()

微任务 vs 宏任务

为了更好地理解微任务,我们把它和宏任务对比一下:

特性 宏任务(Macro Task) 微任务(Micro Task)
例子 setTimeout, setInterval, setImmediate, I/O, UI rendering Promise.then, MutationObserver, queueMicrotask
执行时机 每次事件循环的末尾 当前宏任务执行完毕后
优先级
数量 每次事件循环一个 可以有多个

then与微任务队列

Promise的状态发生改变(从pending变为fulfilledrejected)时,then方法对应的回调函数会被放入微任务队列中。这意味着,即使Promise已经完成了,then的回调函数也不会立即执行,而是要等到当前宏任务执行完毕,且主线程空闲时才会执行。

代码示例:微任务的执行顺序

console.log("开始");

const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);

promise1.then((value) => {
  console.log("`Promise1` resolved:", value);
});

promise2.then((value) => {
  console.log("`Promise2` resolved:", value);
});

setTimeout(() => {
  console.log("`setTimeout`");
}, 0);

console.log("结束");

运行这段代码,你会发现控制台输出的顺序是:

  1. 开始
  2. 结束
  3. Promise1 resolved: 1
  4. Promise2 resolved: 2
  5. setTimeout

这是因为Promise.resolve().then()setTimeout 都是异步任务,但是 Promise.then 是微任务,而 setTimeout 是宏任务。在当前宏任务(也就是主线程的代码)执行完毕后,会先执行微任务队列中的所有任务,然后再执行下一个宏任务。

链式调用:then的进阶用法

then方法最强大的地方在于它可以链式调用。这意味着你可以连续调用多个then方法,形成一个Promise链。每一个then方法都会返回一个新的Promise,这个新的Promise的状态取决于前一个then方法的回调函数的返回值。

代码示例:链式调用

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1);
  }, 1000);
});

promise
  .then((value) => {
    console.log("第一个`then`:", value);
    return value + 1; // 返回一个值,这个值会作为下一个`then`的参数
  })
  .then((value) => {
    console.log("第二个`then`:", value);
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve(value + 1); // 返回一个`Promise`
      }, 500);
    });
  })
  .then((value) => {
    console.log("第三个`then`:", value);
    return value + 1;
  })
  .then((value) => {
    console.log("第四个`then`:", value);
  });

运行这段代码,你会发现控制台会依次输出:

  1. 第一个then: 1
  2. 第二个then: 2
  3. 第三个then: 3
  4. 第四个then: 4

链式调用的规则

链式调用有一些需要注意的规则:

  1. then方法总是返回一个新的Promise
  2. 如果then方法的回调函数返回一个值,那么新的Promise会立即变为fulfilled状态,并将这个值作为fulfilled状态的值传递给下一个then方法。
  3. 如果then方法的回调函数返回一个Promise,那么新的Promise的状态会和这个返回的Promise的状态保持一致。也就是说,新的Promise会等待返回的Promise的状态变为fulfilledrejected,然后才会继续执行下一个then方法。
  4. 如果then方法的回调函数抛出一个错误,那么新的Promise会立即变为rejected状态,并将这个错误作为rejected状态的值传递给下一个catch方法(如果没有catch方法,则会抛出未捕获的错误)。

catch方法:错误处理的利器

catch方法用于捕获Promise链中发生的错误。它可以放在Promise链的末尾,或者放在任何一个then方法之后。

代码示例:catch的用法

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    const randomNumber = Math.random();
    if (randomNumber > 0.5) {
      resolve(1);
    } else {
      reject("出错了!randomNumber: " + randomNumber);
    }
  }, 1000);
});

promise
  .then((value) => {
    console.log("第一个`then`:", value);
    return value + 1;
  })
  .then((value) => {
    console.log("第二个`then`:", value);
    // 模拟一个错误
    throw new Error("故意抛出的错误");
    return value + 1;
  })
  .then((value) => {
    console.log("第三个`then`:", value); // 这行代码不会执行
  })
  .catch((error) => {
    console.error("捕获到错误:", error);
  });

运行这段代码,你会发现控制台会输出:

  1. 第一个then: 1
  2. 第二个then: 2
  3. 捕获到错误: Error: 故意抛出的错误

finally方法:无论成功失败,都要执行

finally方法用于指定无论Promise成功还是失败都要执行的回调函数。finally方法不接收任何参数,并且不能访问Promise的结果或错误。

代码示例:finally的用法

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    const randomNumber = Math.random();
    if (randomNumber > 0.5) {
      resolve(1);
    } else {
      reject("出错了!randomNumber: " + randomNumber);
    }
  }, 1000);
});

promise
  .then((value) => {
    console.log("成功了:", value);
  })
  .catch((error) => {
    console.error("失败了:", error);
  })
  .finally(() => {
    console.log("无论成功还是失败,我都会执行");
  });

运行这段代码,你会发现控制台会输出成功或失败的结果,并且最后都会输出“无论成功还是失败,我都会执行”。

Promise.all:并发执行,等待所有完成

Promise.all方法接收一个Promise数组作为参数,并返回一个新的Promise。只有当数组中所有的Promise都变为fulfilled状态时,新的Promise才会变为fulfilled状态,并将所有Promise的结果放入一个数组中作为fulfilled状态的值。如果数组中有一个Promise变为rejected状态,那么新的Promise会立即变为rejected状态,并将这个Promise的错误作为rejected状态的值。

代码示例:Promise.all的用法

const promise1 = Promise.resolve(1);
const promise2 = new Promise((resolve) => {
  setTimeout(() => {
    resolve(2);
  }, 500);
});
const promise3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject("出错了!");
  }, 1000);
});

Promise.all([promise1, promise2, promise3])
  .then((values) => {
    console.log("所有`Promise`都成功了:", values);
  })
  .catch((error) => {
    console.error("有一个`Promise`失败了:", error);
  });

运行这段代码,你会发现控制台会输出“有一个Promise失败了: 出错了!”。因为promise3最终变为rejected状态。

Promise.race:赛跑机制,谁先完成算谁的

Promise.race方法也接收一个Promise数组作为参数,并返回一个新的Promise。新的Promise的状态会和数组中第一个变为fulfilledrejected状态的Promise保持一致。

代码示例:Promise.race的用法

const promise1 = new Promise((resolve) => {
  setTimeout(() => {
    resolve(1);
  }, 500);
});
const promise2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject("出错了!");
  }, 1000);
});

Promise.race([promise1, promise2])
  .then((value) => {
    console.log("第一个完成的`Promise`成功了:", value);
  })
  .catch((error) => {
    console.error("第一个完成的`Promise`失败了:", error);
  });

运行这段代码,你会发现控制台会输出“第一个完成的Promise成功了: 1”。因为promise1promise2先完成。

总结:Promise的精髓

Promisethen方法是其核心,它通过微任务队列实现了异步操作的调度,并通过链式调用实现了复杂的异步流程控制。掌握then方法以及微任务队列的机制,对于理解和使用Promise至关重要。

一些小技巧

  • 尽量避免在then方法的回调函数中进行耗时的操作,以免阻塞微任务队列的执行。
  • 合理使用catch方法进行错误处理,避免出现未捕获的错误。
  • 根据实际情况选择Promise.allPromise.race,以提高代码的效率。

好了,今天的分享就到这里,希望大家有所收获!如果还有什么疑问,欢迎在评论区留言,我会尽力解答。下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注