Promise的介绍、原理和手写
一、Promise 的诞生:解决回调地狱的终极方案
在 JavaScript 的异步编程发展史上,回调函数曾是处理异步操作的主要手段。但随着业务逻辑的复杂化,回调嵌套的层数越来越深,形成了令人头疼的 "回调地狱"。例如:
TypeScript
fs.readFile('a.txt', 'utf8', (err, data1) => {
fs.readFile(`./${data1}.txt', 'utf8', (err, data2) => {
fs.readFile(`./${data2}.txt', 'utf8', (err, data3) => {
// 深层嵌套的回调逻辑, 从这开始如果想处理异常就已经有点麻烦了
});
});
});
这种代码结构不仅可读性差,维护成本高,而且异常处理困难。Promise 的出现正是为了规范异步编程,通过链式调用让异步逻辑更清晰,状态管理更可控。ECMAScript 6 正式将 Promise 纳入标准,使其成为现代 JavaScript 异步编程的基石。
二、Promise 核心概念解析
(一)Promise 的三种状态
-
pending(进行中):初始状态,异步操作未完成或未拒绝
-
fulfilled(已完成):异步操作成功完成
-
rejected(已拒绝):异步操作失败
状态转换具有单向性:pending可以转换为fulfilled或rejected,但一旦确定状态就无法再改变。
(二)Promise 构造函数
TypeScript
const promise = new Promise((resolve, reject) => {
// 执行异步操作
setTimeout(() => {
const success = true;
if (success) {
resolve('操作成功'); // 状态转为fulfilled
} else {
reject('操作失败'); // 状态转为rejected
}
}, 1000);
});
-
resolve:成功时调用的回调函数,参数为成功状态的返回值
-
reject:失败时调用的回调函数,参数为失败状态的原因
构造函数的手写
Javascript
class MyPromise {
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.fulfilledCallbacks = [];
this.rejectedCallbacks = [];
const resolve = (value) => {
if (this.state !== 'pending') return;
this.state = 'fulfilled';
this.value = value;
// 异步执行回调(模拟微任务)
setTimeout(() => {
this.fulfilledCallbacks.forEach(fn => fn(value));
}, 0);
};
const reject = (reason) => {
if (this.state !== 'pending') return;
this.state = 'rejected';
this.value = reason;
setTimeout(() => {
this.rejectedCallbacks.forEach(fn => fn(reason));
}, 0);
};
try {
executor(resolve, reject);
} catch (error) {
}
}
(三)关键方法详解
then () - 异步结果处理
TypeScript
promise.then(
(result) => { // fulfilled状态处理函数
console.log(result); // 输出"操作成功"
},
(error) => { // rejected状态处理函数
console.error(error);
}
);
then方法的手写
JavaScript
MyPromise.prototype.then = function (onFulfilled, onRejected) {
// 参数默认值处理
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val;
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err; };
const newPromise = new MyPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
// 立即执行,返回值决定新Promise状态
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(newPromise, x, resolve, reject); // 核心解析函数
} catch (error) {
reject(error);
}
}, 0);
}
if (this.state === 'rejected') {
setTimeout(() => {
try {
const x = onRejected(this.value);
resolvePromise(newPromise, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
}
}
}
catch () - 统一错误处理
TypeScript
promise.then(result => {
// 成功处理逻辑
}).catch(error => {
// 统一处理所有拒绝状态
console.error('发生错误:', error);
});
finally () - 最终执行逻辑
TypeScript
promise
.then(result => process(result))
.catch(error => handleError(error))
.finally(() => {
// 无论成功或失败都会执行
console.log('异步操作结束');
});
三、Promise 链式调用的最佳实践
(一)链式调用的实现原理
每次调用then()或catch()都会返回一个新的 Promise 对象,从而实现链式调用。新 Promise 的状态由以下规则决定:
-
若回调函数返回有效值,新 Promise 状态为fulfilled,值为返回值
-
若回调函数抛出错误,新 Promise 状态为rejected,值为错误对象
-
若回调函数返回另一个 Promise,新 Promise 状态由该 Promise 决定
(二)典型应用场景
1. 顺序执行异步操作
TypeScript
fetchUser()
.then(user => fetchOrder(user.id))
.then(order => processOrder(order))
.catch(error => logError(error));
2. 错误处理策略
TypeScript
asyncFunction()
.then(result => {
// 可能抛出错误的操作
if (result.invalid) {
throw new Error('无效结果');
}
return process(result);
})
.catch(error => {
// 可以针对不同错误类型处理
if (error instanceof NetworkError) {
retryRequest();
} else if (error.message === '无效结果') {
showValidationError();
}
});
四、高级特性与静态方法
Promise.all () - 并行执行多个异步操作
TypeScript
const promise1 = fetch('https://api/data1');
const promise2 = fetch('https://api/data2');
Promise.all([promise1, promise2])
.then(responses => responses.map(res => res.json()))
.then(data => console.log('全部数据:', data))
.catch(error => console.error('任一请求失败:', error));
-
输入:Promise 数组
-
输出:包含所有 Promise 结果的数组
-
特点:只要有一个 Promise 拒绝,整体就拒绝
Promise.all()的手写
JavaScript
MyPromise.all = function (promises) {
return new MyPromise((resolve, reject) => {
const results = [];
let count = 0;
promises.forEach((p, index) => {
MyPromise.resolve(p).then((value) => {
results[index] = value;
if (++count === promises.length) {
resolve(results);
}
}, reject); // 第一个失败立即触发reject
});
});
};
Promise.race () - 竞争执行
TypeScript
const timeout = new Promise((_, reject) => {
setTimeout(() => reject(new Error('请求超时')), 5000);
});
const fetchData = fetch('https://api/data');
Promise.race([fetchData, timeout])
.then(data => process(data))
.catch(error => handleError(error));
-
输入:Promise 数组
-
输出:第一个解决(fulfilled/rejected)的 Promise 结果
Promise.race () 的手写
ini
MyPromise.race = function (promises) {
return new MyPromise((resolve, reject) => {
promises.forEach(p => {
MyPromise.resolve(p).then(resolve, reject);
});
});
};
Promise.allSettled () - 获取所有结果(包括拒绝)
TypeScript
const promises = [
Promise.resolve(1),
Promise.reject('失败'),
Promise.resolve(3)
];
Promise.allSettled(promises)
.then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('成功值:', result.value);
} else {
console.error('失败原因:', result.reason);
}
});
});
Promise.allSettled ()的手写
JavaScript
MyPromise.allSettled = function(promises) {
return new MyPromise((resolve) => {
const results = [];
let count = 0;
promises.forEach((p, index) => {
MyPromise.resolve(p).then(
(value) => {
results[index] = { status: 'fulfilled', value };
if (++count === promises.length) resolve(results);
},
(reason) => {
results[index] = { status: 'rejected', reason };
if (++count === promises.length) resolve(results);
}
);
});
});
};
(四)Promise.any () - 获取第一个成功结果
TypeScript
const promises = [
Promise.reject('失败1'),
Promise.resolve('成功'),
Promise.reject('失败2')
];
Promise.any(promises)
.then(value => console.log('第一个成功值:', value))
.catch(error => console.error('所有都失败:', error));
五、Promise 与 async/await 的完美结合
(一)语法糖带来的代码简化
传统 Promise 写法:
TypeScript
fetchUser()
.then(user => fetchProfile(user.id))
.then(profile => saveProfile(profile))
.catch(error => handleError(error));
async/await 写法:
TypeScript
async function saveUserProfile() {
try {
const user = await fetchUser();
const profile = await fetchProfile(user.id);
await saveProfile(profile);
} catch (error) {
handleError(error);
}
}
(二)错误处理机制
-
try/catch捕获整个异步块的错误
-
单个await表达式可以用try/catch单独处理
-
保持与 Promise 相同的错误传递机制
六、最佳实践与注意事项
(一)错误处理原则
-
始终添加catch处理程序或使用try/catch
-
避免未处理的 Promise 拒绝(可通过全局监听unhandledrejection事件)
-
在链式调用中保持错误处理的连续性
(二)性能优化
-
避免创建不必要的 Promise 对象
-
合理使用并行执行(Promise.all)替代顺序执行
-
注意内存管理,及时释放不再需要的 Promise 引用
(三)常见反模式
TypeScript
// 反模式:在then中返回非Promise值时未正确处理
somePromise.then(() => {
return makeRequest(); // 假设makeRequest不是Promise
}).then(result => { /* 这里result会是undefined */ });
// 正确做法:确保返回Promise或有效值
somePromise.then(() => {
return Promise.resolve(makeRequest());
}).then(result => { /* 正确获取结果 */ });
七、Promise 的浏览器兼容性
兼容性现状
-
现代浏览器(Chrome, Firefox, Edge, Safari 10+)完全支持 Promise
-
IE 浏览器需要 polyfill(如 es6-promise 库)
八、总结
当然,现在前端卷成这样的环境下,掌握 Promise 早已不是前端开发者的加分项,很多时候它只是面试八股里的一道例题,但这不代表它不重要。
它不仅是一种技术实现,更代表着一套完整的异步思维模式 ------ 当你能熟练运用状态管理、结果传递和流程控制这三大核心武器时,再复杂的异步难题也会迎刃而解。
Promise 的出现标志着 JavaScript 异步编程的成熟,它通过标准化的状态管理和链式调用机制,解决了回调地狱的难题。结合 async/await 语法糖,使得异步代码可以写成同步风格,极大提升了代码的可读性和可维护性。
建议大伙在实际项目中多使用 Promise 进行异步操作封装,遵循最佳实践,也愿诸位写出更优雅、更健壮的代码。