一、引言
JavaScript 是一种单线程语言,这意味着它一次只能执行一个任务。为了处理异步操作(如网络请求、定时器等),JavaScript 使用了事件循环机制来管理任务的执行顺序。了解宏任务、微任务以及同步任务的区别对于掌握 JavaScript 的异步编程至关重要。
二、同步任务、宏任务与微任务
同步任务:
- 同步任务指的是在主线程上排队执行的任务,只有前一个任务执行完毕后,下一个任务才能开始。
常见的同步任务有
赋值语句
,输出语句
,运算操作
和条件循环结构控制流
语句。
宏任务(Macrotask):
- 每个宏任务执行完之后,都会检查并执行所有的微任务。常见的宏任务包括
setTimeout
、setInterval
、I/O、UI渲染等。
微任务(Microtask):
- 微任务队列中的所有任务会在当前宏任务结束后立即执行。常见的微任务包括
Promise.then/catch/finally
、MutationObserver
等。
三、Promise状态与方法
1、状态
每个 Promise
实例都有且仅有以下三种状态之一:
-
Pending(待定) :
- 初始状态,表示
Promise
尚未被解决或拒绝。 - 在这个状态下,
Promise
的结果是未知的。
- 初始状态,表示
-
Fulfilled(已成功) :
- 表示
Promise
已经被成功解决。 - 通常通过调用
resolve
方法进入此状态。
- 表示
-
Rejected(已失败) :
- 表示
Promise
已经被拒绝。 - 通常通过调用
reject
方法进入此状态。
- 表示
Promise
的状态只能从 pending
转变为 fulfilled
或 rejected
,并且一旦状态发生变化,就不会再改变。这种不可逆的状态流转机制确保了 Promise
的可靠性。
// 创建一个Promise
var promiseInstance = new Promise(() => {});
// resolve
var resolvedPromise = Promise.resolve();
// reject
var rejectedPromise = Promise.reject();
console.log(promiseInstance, resolvedPromise,rejectedPromise);
2、如何观察 Promise 的状态变化
由于 JavaScript 的设计限制,我们无法直接访问 Promise
的内部状态(如 pending
、fulfilled
或 rejected
)。但我们可以通过以下方法间接观察状态变化:
-
通过
.then
和.catch
- 使用
.then
可以捕获fulfilled
状态的结果。 - 使用
.catch
可以捕获rejected
状态的错误。
- 使用
const promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("Success!"), 1000); });
promise .then((result) => console.log("Resolved:", result))
.catch((error) => console.log("Rejected:", error));
// 输出: Resolved: Success!
- 通过
Promise.resolve()
和Promise.reject()
Promise.resolve()
返回一个已经被解决的Promise
。Promise.reject()
返回一个已经被拒绝的Promise
。
const resolvedPromise = Promise.resolve("Resolved!");
const rejectedPromise = Promise.reject("Rejected!");
console.log(resolvedPromise);
// 输出: Promise { <fulfilled>: 'Resolved!' }
console.log(rejectedPromise);
// 输出: Promise { <rejected>: 'Rejected!' }
3、Promise静态方法
方法/属性 | 特点 | 使用场景 |
---|---|---|
Promise.all | 等待所有 Promise 成功解决;若有一个失败,则立即失败。 | 需要确保所有任务都成功完成时使用。 |
Promise.race | 返回第一个完成的 Promise 的结果(无论是成功还是失败)。 | 需要快速响应第一个完成的任务时使用。 |
Promise.allSettled | 等待所有 Promise 完成,无论成功或失败;返回每个 Promise 的状态和结果。 | 需要知道所有任务的最终状态时使用。 |
Promise.any | 返回第一个成功解决的 Promise ;如果所有 Promise 都失败,则返回 AggregateError 。 | 只需要一个任务成功即可时使用。 |
finally | 无论 Promise 是成功解决 (fulfilled ) 还是失败 (rejected ),finally 方法都会执行。 | 执行清理工作或后续步骤,不论 Promise 结果。 |
四、了解async和await
1. async
函数
- 使用
async
关键字声明的函数总是返回一个Promise
。 - 如果函数返回一个普通值,该值会被自动包装成一个已解决的
Promise
。 - 如果函数抛出异常,则返回的
Promise
会被拒绝。
2. await
关键字
await
只能在async
函数内部使用。- 它会让当前
async
函数暂停执行,直到等待的Promise
被解决或拒绝。 - 当
Promise
被解决后,await
表达式的值就是Promise
的结果。
3. await
阻碍主线程?
JavaScript 是单线程语言,所有代码都在主线程上运行。当我们在代码中使用 await
时,它并不会真正“阻塞”整个程序的执行,而是通过事件循环机制来实现“暂停”的效果。
4. await
的底层机制
- 当遇到
await
时,JavaScript 引擎会暂停当前async
函数的执行,并将控制权交还给事件循环。 - 等待的
Promise
被解决后,await
后续的代码会被放入微任务队列(Microtask Queue) 中。 - 微任务队列中的代码会在当前宏任务(macrotask)完成后立即执行。
示例:观察 await
对代码执行的影响
console.log("Start");
async function asyncFunction()
{
console.log("Async Function Start");
await new Promise(resolve => setTimeout(resolve, 1000));
console.log("Async Function End");
}
asyncFunction();
console.log("End");
//输出结果如下:
// Start
// Async Function Start
// End
// (1秒后)Async Function End
例题总结
async function asyncFunc1() {
console.log('asyncFunc1 start');
await asyncFunc2();
console.log('asyncFunc1 end');
}
async function asyncFunc2() {
console.log('asyncFunc2 start');
return new Promise((resolve) => {
setTimeout(() => {
console.log('setTimeout in asyncFunc2');
resolve();
}, 0);
});
}
console.log('script start');
setTimeout(() => {
console.log('setTimeout outside');
}, 0);
asyncFunc1();
new Promise((resolve) => {
console.log('promise creation');
resolve();
}).then(() => {
console.log('first then');
}).then(() => {
console.log('second then');
});
console.log('script end');
//输出顺序
// script start
// asyncFunc1 start
// asyncFunc2 start
// promise creation
// script end
// first then
// second then
// setTimeout outside
// setTimeout in asyncFunc2
// asyncFunc1 end
输出顺序的原因
-
同步代码优先执行:
- 所有同步代码(如
console.log
和Promise
构造器内部的代码)会立即执行。
- 所有同步代码(如
-
微任务优先于宏任务:
- 当前宏任务完成后,微任务队列中的任务会优先执行。
Promise.then
和await
后续代码都会被注册为微任务。
-
宏任务按注册顺序执行:
setTimeout
注册的宏任务会在微任务队列清空后依次执行。
-
await
的暂停机制:await
会让当前async
函数暂停执行,并将后续代码注册为微任务。- 只有当等待的
Promise
被解决后,这些微任务才会被执行。