浏览器揭密异步:Promise 初探
2023-11-22 08:17:28
浏览器运行机制的复杂性与 JS 异步编程息息相关。我们先从浏览器多线程与 JS 单线程的对比开始,理解它们如何共存。接着,我们将深入微任务与宏任务的世界,发掘它们对 JS 异步代码执行顺序的影响。最后,我们将聚焦 JS Promise,从原理到实践,全面解析这一异步编程利器。
多线程浏览器与单线程 JavaScript
现代浏览器往往是多线程的,以便更好地利用多核 CPU 的计算能力。浏览器通常拥有多个线程,包括主线程、网络线程、渲染线程等。其中,主线程负责执行 JavaScript 代码、处理用户输入、更新 DOM 等。其他线程负责网络请求、页面渲染等任务。
然而,JavaScript 却是单线程的,这意味着在同一时刻,主线程只能执行一个任务。当主线程执行一个任务时,其他任务必须等待。这一特性既有优势也有劣势。优势在于,它简化了编程模型,避免了多线程编程中的各种并发问题。劣势在于,当一个任务执行时间过长时,整个浏览器都会卡顿。
微任务与宏任务
为了解决单线程带来的性能问题,浏览器引入了微任务和宏任务的概念。微任务是指那些需要立即执行的任务,而宏任务是指那些可以稍后执行的任务。浏览器会在执行完所有微任务之后再执行宏任务。
常见的微任务包括:
- Promise 的 then() 和 catch() 回调函数
- MutationObserver 的回调函数
- DOM 事件的回调函数
常见的宏任务包括:
- setTimeout() 和 setInterval() 的回调函数
- requestAnimationFrame() 的回调函数
- I/O 操作的回调函数
JavaScript Promise
Promise 是 JavaScript 中用于处理异步操作的利器。它是一个对象,代表一个异步操作的结果。Promise 的状态可以是 pending、fulfilled 或 rejected。
- pending:表示异步操作尚未完成。
- fulfilled:表示异步操作已成功完成,并有结果返回。
- rejected:表示异步操作已失败,并有错误信息返回。
我们可以通过 then() 方法来监听 Promise 的状态变化。then() 方法接收两个参数,第一个参数是 fulfilled 状态的回调函数,第二个参数是 rejected 状态的回调函数。当 Promise 的状态变为 fulfilled 时,第一个回调函数会被调用,并传入 Promise 的结果。当 Promise 的状态变为 rejected 时,第二个回调函数会被调用,并传入 Promise 的错误信息。
手写 Promise
我们可以使用 ES6 的 class 来实现一个简化版的 Promise。代码如下:
class Promise {
constructor(executor) {
this.state = 'pending';
this.result = undefined;
this.error = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (result) => {
if (this.state !== 'pending') return;
this.state = 'fulfilled';
this.result = result;
this.onFulfilledCallbacks.forEach(callback => callback(result));
};
const reject = (error) => {
if (this.state !== 'pending') return;
this.state = 'rejected';
this.error = error;
this.onRejectedCallbacks.forEach(callback => callback(error));
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
return new Promise((resolve, reject) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
const result = onFulfilled(this.result);
resolve(result);
} catch (error) {
reject(error);
}
}, 0);
} else if (this.state === 'rejected') {
setTimeout(() => {
try {
const error = onRejected(this.error);
resolve(error);
} catch (error) {
reject(error);
}
}, 0);
} else {
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
const result = onFulfilled(this.result);
resolve(result);
} catch (error) {
reject(error);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const error = onRejected(this.error);
resolve(error);
} catch (error) {
reject(error);
}
}, 0);
});
}
});
}
}
总结
浏览器异步编程是前端开发中必不可少的一环。通过对浏览器运行机制、微任务与宏任务、以及 JS Promise 的深入理解,我们可以更加娴熟地掌握异步编程技巧,编写出更加高效、健壮的代码。希望本文能帮助您更好地理解这些概念,并在实际开发中灵活运用。