Promise 封装回调式异步
解释如何用 Promise 封装回调风格的异步操作,如 XMLHttpRequest、setTimeout 等。
#tech / dev / frontend
#type / concept
#status / growing
#resource / javascript
#resource / ecmascript
[!info] related notes
- 所属 MOC: ECMAScript MOC, 前端基础 MOC
- 前置概念: ECMAScript Promise 与异步
- 并列概念: JS 事件循环, 异步错误处理
Promise 封装回调式异步
这篇笔记回答一个问题:如何把回调风格的异步 API 转换成 Promise 风格。
一句话定义
Promise 封装的核心是在 Promise 构造函数中执行回调式异步操作,成功时调用 resolve,失败时调用 reject。
核心模式
回调式异步和 Promise 的根本差异在于:回调是事件驱动的,Promise 是状态驱动的。封装的本质是把”事件结果”映射为”状态结果”。
封装 XMLHttpRequest
function ajaxPromise(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.responseText);
} else {
reject(new Error(xhr.statusText));
}
};
xhr.onerror = () => reject(new Error('network error'));
xhr.send();
});
}
封装 setTimeout
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// 使用
await delay(1000);
console.log('1 秒后执行');
封装 Node.js 风格回调
function readFilePromise(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, 'utf8', (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
}
通用 promisify 工具
function promisify(fn) {
return function(...args) {
return new Promise((resolve, reject) => {
fn(...args, (err, result) => {
if (err) reject(err);
else resolve(result);
});
});
};
}
常见边界
- 如果回调会被多次调用(如事件监听),Promise 不适合,应该用 EventEmitter 或 Observable
- Promise 只能 settle 一次,多次 resolve / reject 只有第一次生效
- 封装时要注意清理资源(如 abort XHR、clearTimeout)
最小例子
function fetchJSON(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = () => resolve(JSON.parse(xhr.responseText));
xhr.onerror = () => reject(new Error('failed'));
xhr.send();
});
}
fetchJSON('/api/data')
.then(data => console.log(data))
.catch(err => console.error(err));