一、Promise 的底层实现机制
1.1 Promise 规范与状态管理
Promise 是基于 Promises/A+ 规范实现的,本质上是一个状态机,具有三种状态:
- pending(等待态)
- fulfilled(完成态)
- rejected(拒绝态)
一旦状态变更为 fulfilled 或 rejected,就不再改变。这种状态封装是 Promise 实现可靠性的核心。
// Promise 简易实现的核心部分
function MyPromise(executor) {
this.state = 'pending'; // 初始状态
this.value = undefined; // 成功值
this.reason = undefined; // 失败原因
this.onFulfilledCallbacks = []; // 成功回调队列
this.onRejectedCallbacks = []; // 失败回调队列
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onFulfilledCallbacks.forEach(fn => fn(value));
}
};
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn(reason));
}
};
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
1.2 微任务队列与事件循环
Promise 的处理函数(.then()
、.catch()
、.finally()
)会被放入微任务队列中执行,而不是直接执行或者放入宏任务队列:
// Promise 处理为微任务的示例
console.log('同步开始');
Promise.resolve().then(() => {
console.log('Promise微任务');
});
setTimeout(() => {
console.log('setTimeout宏任务');
}, 0);
console.log('同步结束');
// 输出顺序:
// 同步开始
// 同步结束
// Promise微任务
// setTimeout宏任务
底层实现上,V8 引擎会维护一个 Promise Jobs 队列,它在事件循环中的执行优先级高于宏任务。
1.3 Promise 链式调用实现
then
方法的实现是 Promise 链式调用的核心,它总是返回一个新的 Promise:
MyPromise.prototype.then = function(onFulfilled, onRejected) {
// 确保参数为函数
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
// 创建新的 Promise 实例实现链式调用
const promise2 = new MyPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
queueMicrotask(() => {
try {
const x = onFulfilled(this.value);
// 处理返回值(可能是普通值或Promise)
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
}
if (this.state === 'rejected') {
queueMicrotask(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
}
// 处理 pending 状态,将回调存入队列
if (this.state === 'pending') {
this.onFulfilledCallbacks.push(() => {
queueMicrotask(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
this.onRejectedCallbacks.push(() => {
queueMicrotask(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
});
return promise2;
};
二、Async/Await 的底层实现机制
2.1 生成器与协程
Async/Await 实际上是 Generator 和 Promise 的语法糖,底层实现依赖于 JavaScript 引擎的协程(Coroutine)机制:
// Generator 实现原理类似
function* generatorExample() {
const result1 = yield Promise.resolve(1);
console.log(result1); // 1
const result2 = yield Promise.resolve(2);
console.log(result2); // 2
return 'done';
}
// 手动执行生成器
function runGenerator(gen) {
const g = gen();
function handle(result) {
if (result.done) return Promise.resolve(result.value);
return Promise.resolve(result.value).then(
res => handle(g.next(res)),
err => handle(g.throw(err))
);
}
return handle(g.next());
}
// 模拟 async/await
runGenerator(generatorExample).then(res => console.log(res));
2.2 从编译到执行的转换过程
Async/Await 代码在编译阶段会被转换为状态机,类似于以下过程:
// 原始 async 函数
async function fetchData() {
const user = await getUserData();
const orders = await getOrdersData(user.id);
return { user, orders };
}
// 被转换为(简化版)
function fetchData() {
return new Promise((resolve, reject) => {
let state = 0; // 状态机状态
let user, orders;
function next(value) {
switch (state) {
case 0:
state = 1;
return Promise.resolve(getUserData()).then(nextStep, handleError);
case 1:
user = value;
state = 2;
return Promise.resolve(getOrdersData(user.id)).then(nextStep, handleError);
case 2:
orders = value;
resolve({ user, orders });
return;
}
}
function handleError(error) {
reject(error);
}
function nextStep(value) {
try {
return next(value);
} catch (e) {
handleError(e);
}
}
next();
});
}
2.3 暂停与恢复执行
Async/Await 的一个关键特性是能够暂停函数执行,等待 Promise 解决后再恢复执行。这个过程在引擎内部是通过保存和恢复执行上下文实现的:
async function processTasks() {
console.log("开始处理");
// 暂停执行,将控制权交还给事件循环
const result1 = await task1();
console.log("任务1完成:", result1);
// 再次暂停执行
const result2 = await task2(result1);
console.log("任务2完成:", result2);
return "所有任务完成";
}
JavaScript 引擎会保存函数的局部变量、执行位置等信息,当 Promise 解决后,重新载入这些信息并从暂停点继续执行。
三、错误处理最佳实践
3.1 Promise 错误冒泡与捕获
Promise 具有错误冒泡机制,错误会沿着 Promise 链向下传递,直到被捕获:
// 不推荐的方式:每个 then 单独处理错误
fetchData()
.then(data => {
try {
return processData(data);
} catch (e) {
console.error("处理数据错误", e);
throw e; // 重新抛出以继续传递
}
})
.then(result => {
try {
return formatResult(result);
} catch (e) {
console.error("格式化结果错误", e);
throw e;
}
});
// 推荐的方式:利用错误冒泡
fetchData()
.then(data => processData(data))
.then(result => formatResult(result))
.catch(error => {
// 集中处理所有错误
console.error("操作链中出现错误:", error);
// 根据错误类型进行不同处理
if (error instanceof NetworkError) {
// 处理网络错误
} else if (error instanceof ValidationError) {
// 处理验证错误
}
});
3.2 Async/Await 错误处理模式
Async/Await 可以使用传统的 try/catch 结构进行错误处理,使代码更加清晰:
// 基础 try/catch 模式
async function fetchUserData(userId) {
try {
const user = await database.users.findById(userId);
const permissions = await database.permissions.findByUserId(userId);
return { user, permissions };
} catch (error) {
console.error("获取用户数据失败:", error);
throw new UserDataError(error.message, { userId, originalError: error });
}
}
// 更高级:错误边界模式
async function withErrorBoundary(asyncFn, errorHandler) {
try {
return await asyncFn();
} catch (error) {
return errorHandler(error);
}
}
// 使用错误边界
const userData = await withErrorBoundary(
() => fetchUserData(123),
(error) => {
reportErrorToMonitoring(error);
return { user: null, permissions: [], error };
}
);
3.3 自定义错误类与错误分类
定义明确的错误类有助于更精确地处理不同类型的错误:
// 自定义错误基类
class AppError extends Error {
constructor(message, options = {}) {
super(message);
this.name = this.constructor.name;
this.timestamp = new Date();
this.code = options.code || 'ERR_UNKNOWN';
this.data = options.data || {};
// 记录错误发生的堆栈信息
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
}
toJSON() {
return {
name: this.name,
message: this.message,
code: this.code,
timestamp: this.timestamp,
data: this.data
};
}
}
// 特定错误类型
class NetworkError extends AppError {
constructor(message, options = {}) {
super(message, { code: 'ERR_NETWORK', ...options });
}
}
class ValidationError extends AppError {
constructor(message, options = {}) {
super(message, { code: 'ERR_VALIDATION', ...options });
this.validationErrors = options.validationErrors || [];
}
toJSON() {
return {
...super.toJSON(),
validationErrors: this.validationErrors
};
}
}
// 使用自定义错误
async function validateAndSubmitForm(formData) {
try {
const errors = validateForm(formData);
if (errors.length > 0) {
throw new ValidationError("表单验证失败", {
validationErrors: errors,
data: { formId: formData.id }
});
}
return await submitForm(formData);
} catch (error) {
if (error instanceof ValidationError) {
// 处理验证错误
showValidationErrors(error.validationErrors);
} else if (error instanceof NetworkError) {
// 处理网络错误
retryWithExponentialBackoff(submitForm, formData);
} else {
// 未知错误
reportToErrorTracking(error);
showGenericErrorMessage();
}
throw error; // 或者可以返回一个默认值
}
}
3.4 异步操作的终止与取消
Promise 原生不支持取消,但可以通过 AbortController 接口实现可取消的异步操作:
function fetchWithTimeout(url, options = {}) {
const { timeout = 5000, ...fetchOptions } = options;
const controller = new AbortController();
const { signal } = controller;
// 创建超时计时器
const timeoutId = setTimeout(() => controller.abort(), timeout);
// 发起请求
const promise = fetch(url, { signal, ...fetchOptions })
.then(response => {
clearTimeout(timeoutId);
if (!response.ok) {
throw new NetworkError(`HTTP error ${response.status}`, {
status: response.status,
data: { url }
});
}
return response.json();
})
.catch(error => {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new NetworkError(`请求超时(${timeout}ms)`, {
code: 'ERR_TIMEOUT',
data: { url, timeout }
});
}
throw error;
});
// 增强promise,添加取消能力
promise.abort = () => controller.abort();
return promise;
}
// 使用示例
async function loadUserProfile(userId) {
const request = fetchWithTimeout(`/api/users/${userId}`, { timeout: 3000 });
try {
const userData = await request;
updateUserInterface(userData);
} catch (error) {
if (error.code === 'ERR_TIMEOUT') {
showTimeoutMessage();
} else {
showErrorMessage(error.message);
}
}
// 如果需要,可以主动取消
// someEvent.on('cancelRequest', () => request.abort());
}
四、高级异步模式与实战案例
4.1 并发控制模式
在实际应用中,常常需要控制异步操作的并发数量,避免资源过载:
// 并发限制器
class ConcurrencyManager {
constructor(limit = 5) {
this.limit = limit;
this.running = 0;
this.queue = [];
}
async add(fn) {
// 返回一个Promise,表示任务的最终结果
return new Promise((resolve, reject) => {
// 将任务及其解决/拒绝函数加入队列
this.queue.push({ fn, resolve, reject });
this.run();
});
}
async run() {
// 如果有等待任务且未达到并发限制
if (this.queue.length > 0 && this.running < this.limit) {
this.running++;
const { fn, resolve, reject } = this.queue.shift();
try {
// 执行任务
const result = await fn();
resolve(result);
} catch (error) {
reject(error);
} finally {
this.running--;
// 继续处理队列中的下一个任务
this.run();
}
}
}
}
// 使用示例:批量处理图片
async function processImages(imageUrls) {
const concurrency = new ConcurrencyManager(3); // 最多同时处理3张图片
const results = [];
for (const url of imageUrls) {
// 添加到并发管理器
results.push(
concurrency.add(async () => {
const image = await downloadImage(url);
const processed = await applyFilters(image);
return await uploadProcessedImage(processed);
})
);
}
// 等待所有图片处理完成
return Promise.all(results);
}
4.2 实战案例:可恢复的文件上传
这个案例融合了前面讨论的多种技术,实现了一个带有重试、进度报告和错误处理的文件上传系统:
class ResumableUploader {
constructor(file, url, options = {}) {
this.file = file;
this.url = url;
this.chunkSize = options.chunkSize || 1024 * 1024; // 默认1MB
this.retries = options.retries || 3;
this.retryDelay = options.retryDelay || 1000; // 默认1秒
this.onProgress = options.onProgress || (() => {});
this.onError = options.onError || (() => {});
this.onSuccess = options.onSuccess || (() => {});
this.uploadId = null;
this.chunks = this._createChunks();
this.controller = new AbortController();
this.isAborted = false;
}
_createChunks() {
const chunks = [];
let start = 0;
while(start < this.file.size) {
const end = Math.min(start + this.chunkSize, this.file.size);
chunks.push({
blob: this.file.slice(start, end),
start,
end,
progress: 0,
status: 'pending', // pending, uploading, failed, success
retries: 0
});
start = end;
}
return chunks;
}
async start() {
try {
// 初始化上传
this.uploadId = await this._initializeUpload();
// 上传所有分块
await this._uploadChunks();
if (this.isAborted) return null;
// 完成上传
const result = await this._finalizeUpload();
this.onSuccess(result);
return result;
} catch (error) {
if (!this.isAborted) {
this.onError(error);
}
throw error;
}
}
abort() {
this.isAborted = true;
this.controller.abort();
}
async _initializeUpload() {
return await this._retryOperation(async () => {
const response = await fetch(`${this.url}/initialize`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
filename: this.file.name,
contentType: this.file.type,
size: this.file.size,
totalChunks: this.chunks.length
}),
signal: this.controller.signal
});
if (!response.ok) {
throw new NetworkError(`初始化上传失败: ${response.status}`, {
status: response.status
});
}
const data = await response.json();
return data.uploadId;
});
}
async _uploadChunks() {
// 创建并发控制器
const concurrency = new ConcurrencyManager(3);
// 为每个块创建上传任务
const uploadTasks = this.chunks.map((chunk, index) => {
return concurrency.add(async () => {
if (this.isAborted) return;
return await this._uploadChunk(chunk, index);
});
});
// 等待所有块上传完成
await Promise.all(uploadTasks);
}
async _uploadChunk(chunk, index) {
chunk.status = 'uploading';
return this._retryOperation(async () => {
if (this.isAborted) return;
try {
const formData = new FormData();
formData.append('uploadId', this.uploadId);
formData.append('chunkIndex', index);
formData.append('chunk', chunk.blob);
const xhr = new XMLHttpRequest();
// 监听上传进度
xhr.upload.addEventListener('progress', (event) => {
if (event.lengthComputable) {
const chunkProgress = event.loaded / event.total;
chunk.progress = chunkProgress;
this._updateOverallProgress();
}
});
// 包装XHR为Promise
const uploadPromise = new Promise((resolve, reject) => {
xhr.open('POST', `${this.url}/chunk`);
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
chunk.status = 'success';
chunk.progress = 1;
this._updateOverallProgress();
resolve(JSON.parse(xhr.responseText));
} else {
chunk.status = 'failed';
reject(new NetworkError(`上传分块失败: ${xhr.status}`, { status: xhr.status }));
}
};
xhr.onerror = () => {
chunk.status = 'failed';
reject(new NetworkError('网络错误'));
};
xhr.onabort = () => {
reject(new Error('上传已取消'));
};
});
// 连接到abort控制器
this.controller.signal.addEventListener('abort', () => xhr.abort());
// 发送请求
xhr.send(formData);
return await uploadPromise;
} catch (error) {
chunk.status = 'failed';
throw error;
}
}, chunk);
}
async _finalizeUpload() {
return await this._retryOperation(async () => {
const response = await fetch(`${this.url}/finalize`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
uploadId: this.uploadId
}),
signal: this.controller.signal
});
if (!response.ok) {
throw new NetworkError(`完成上传失败: ${response.status}`, { status: response.status });
}
return await response.json();
});
}
async _retryOperation(operation, chunk = null) {
let lastError;
let retries = chunk ? chunk.retries : 0;
const maxRetries = chunk ? this.retries : this.retries * 2; // 非分块操作允许更多重试
while (retries < maxRetries) {
try {
return await operation();
} catch (error) {
if (this.isAborted) throw new Error('上传已取消');
lastError = error;
retries++;
if (chunk) {
chunk.retries = retries;
}
// 如果达到最大重试次数,抛出错误
if (retries >= maxRetries) {
throw new Error(`操作失败,已重试${retries}次: ${lastError.message}`);
}
// 指数退避策略
const delay = this.retryDelay * Math.pow(2, retries - 1);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw lastError;
}
_updateOverallProgress() {
if (this.isAborted) return;
// 计算总体进度
const totalProgress = this.chunks.reduce((sum, chunk) => sum + chunk.progress, 0) / this.chunks.length;
// 报告进度
this.onProgress({
totalProgress,
uploadedBytes: this.chunks.reduce((sum, chunk) => sum + (chunk.blob.size * chunk.progress), 0),
totalBytes: this.file.size
});
}
}
// 使用示例
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', async (e) => {
const file = e.target.files[0];
if (!file) return;
const uploader = new ResumableUploader(file, 'https://api.example.com/upload', {
onProgress: (progress) => {
document.getElementById('progressBar').value = progress.totalProgress * 100;
document.getElementById('progressText').textContent =
`${Math.round(progress.totalProgress * 100)}% (${formatBytes(progress.uploadedBytes)}/${formatBytes(progress.totalBytes)})`;
},
onError: (error) => {
console.error('上传错误:', error);
document.getElementById('statusText').textContent = `上传失败: ${error.message}`;
},
onSuccess: (result) => {
document.getElementById('statusText').textContent = '上传成功!';
document.getElementById('fileLink').href = result.fileUrl;
document.getElementById('fileLink').textContent = result.fileName;
}
});
document.getElementById('cancelButton').addEventListener('click', () => {
uploader.abort();
document.getElementById('statusText').textContent = '上传已取消';
});
document.getElementById('statusText').textContent = '开始上传...';
try {
await uploader.start();
} catch (e) {
console.error('上传过程中发生错误:', e);
}
});
// 工具函数:格式化字节大小
function formatBytes(bytes, decimals = 2) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(decimals)) + ' ' + sizes[i];
}
五、总结与最佳实践
5.1 Promise/Async 最佳实践汇总
-
总是返回 Promise
- 异步函数应始终返回 Promise,保持接口一致性
- 避免混合使用回调和 Promise
-
使用 async/await 代替链式 then
- 提高代码可读性和维护性
- 使错误处理更加直观
-
合理运用并行与串行
- 使用
Promise.all
并行处理独立操作 - 使用
for...of
循环和 await 实现串行操作
- 使用
-
集中处理错误
- 使用自定义错误类进行分类
- 在适当的层级处理错误,避免在每个 Promise 中都处理
-
冷静处理拒绝状态
- 不要忽略 Promise 的拒绝状态
- 使用
Promise.allSettled
处理多个可能失败的 Promise
-
取消长时间运行的操作
- 使用 AbortController 实现可取消的操作
- 在组件卸载或状态变化时及时取消
-
避免回调地狱的等价物
- 避免嵌套 async/await 函数
- 提取有意义的函数名,而不是内联匿名函数
5.2 未来展望
随着 JavaScript 的发展,异步编程模型将继续演进:
-
可观察对象(Observable):处理多值异步流,已在 RxJS 等库中实现,可能成为语言标准
-
顶层 await:在模块顶层使用 await,无需包裹在 async 函数中
-
错误处理增强:更好的错误传播和处理机制
-
原生取消支持:语言层面对异步操作取消的官方支持
Promise 和 Async/Await 已经显著改善了 JavaScript 的异步编程体验,但掌握其底层机制和最佳实践对于编写高效、可维护的代码至关重要。通过理解其工作原理和采用合适的错误处理策略,我们可以充分发挥 JavaScript 异步编程模型的优势。