Promise 知识体系
一、Promise 基础概念
1. 什么是 Promise
Promise 是 JavaScript 中用于处理异步操作的对象,它代表了一个可能现在、将来或永远不会完成的操作的结果。Promise 是 ES6 引入的标准,用于解决传统回调函数带来的”回调地狱”问题。
Promise 最初被提出是在 E 语言中,它是基于并列/并行处理设计的一种编程语言。在 JavaScript 中,Promise 是基于 Promises/A+ 规范实现的。
2. Promise 的状态
Promise 有三种状态:
- Pending(进行中):初始状态,表示异步操作尚未完成
- Fulfilled(已成功):表示异步操作成功完成,且有一个确定的值
- Rejected(已失败):表示异步操作失败,且有一个确定的错误原因
3. Promise 的状态吸收
Promise 的状态一旦改变,就会被”吸收”(也称为”状态锁定”),不会再发生变化。这意味着:
- 从 Pending 变为 Fulfilled 后,不能再变为 Rejected
- 从 Pending 变为 Rejected 后,不能再变为 Fulfilled
- 状态一旦确定,就不可逆转
这种特性确保了 Promise 的行为是可预测的,避免了状态的意外变化。
二、Promise 的基本用法
1. 创建 Promise
// 使用 Promise 构造函数创建 Promise
const promise = new Promise((resolve, reject) => {
// 异步操作
if (/* 操作成功 */) {
resolve(value); // 将 Promise 状态从 Pending 变为 Fulfilled
} else {
reject(error); // 将 Promise 状态从 Pending 变为 Rejected
}
});
2. Promise 的方法
2.1 then()
then()
方法用于指定 Promise 状态变为 Fulfilled 时的回调函数,以及可选的 Promise 状态变为 Rejected 时的回调函数。
promise.then(
value => {
// Promise 成功时的处理
},
error => {
// Promise 失败时的处理(可选)
}
);
2.2 catch()
catch()
方法用于指定 Promise 状态变为 Rejected 时的回调函数,相当于 then(undefined, onRejected)
。
promise.catch(error => {
// Promise 失败时的处理
});
2.3 finally()
finally()
方法用于指定不管 Promise 状态如何变化都会执行的回调函数。
promise.finally(() => {
// 不管 Promise 成功还是失败都会执行
});
3. Promise 链式调用
Promise 的一个重要特性是支持链式调用,每次调用 then()
或 catch()
方法都会返回一个新的 Promise 对象。
promise
.then(value1 => {
// 处理 value1
return newValue1; // 返回一个值,会被包装成 Promise
})
.then(value2 => {
// 处理 value2
return Promise.resolve(newValue2); // 返回一个 Promise
})
.catch(error => {
// 处理前面所有 Promise 中的错误
console.error(error);
return recoveryValue; // 可以返回一个恢复值继续链式调用
})
.then(value3 => {
// 处理 value3 或 recoveryValue
});
三、Promise 的静态方法
1. Promise.resolve()
Promise.resolve()
方法返回一个以给定值解析后的 Promise 对象。
// 等同于 new Promise(resolve => resolve(value))
const promise = Promise.resolve(value);
2. Promise.reject()
Promise.reject()
方法返回一个带有拒绝原因的 Promise 对象。
// 等同于 new Promise((resolve, reject) => reject(reason))
const promise = Promise.reject(reason);
3. Promise.all()
Promise.all()
方法接收一个 Promise 对象数组作为参数,返回一个新的 Promise 对象。当所有 Promise 都成功时,返回的 Promise 状态变为 Fulfilled,值为所有 Promise 结果组成的数组;当任意一个 Promise 失败时,返回的 Promise 状态变为 Rejected,值为第一个失败的 Promise 的错误原因。
const promises = [promise1, promise2, promise3];
Promise.all(promises).then(
values => {
// 所有 Promise 都成功时的处理
console.log(values); // [value1, value2, value3]
},
error => {
// 任意一个 Promise 失败时的处理
}
);
4. Promise.race()
Promise.race()
方法接收一个 Promise 对象数组作为参数,返回一个新的 Promise 对象。当数组中任意一个 Promise 的状态变化时,返回的 Promise 状态跟随该 Promise 变化。
const promises = [promise1, promise2, promise3];
Promise.race(promises).then(
value => {
// 第一个完成的 Promise 成功时的处理
},
error => {
// 第一个完成的 Promise 失败时的处理
}
);
5. Promise.allSettled()
Promise.allSettled()
方法接收一个 Promise 对象数组作为参数,返回一个新的 Promise 对象。当所有 Promise 都完成(无论成功或失败)时,返回的 Promise 状态变为 Fulfilled,值为所有 Promise 结果的状态和值组成的对象数组。
const promises = [promise1, promise2, promise3];
Promise.allSettled(promises).then(results => {
// results 是一个对象数组,每个对象表示对应的 Promise 结果
// { status: 'fulfilled', value: ... } 或 { status: 'rejected', reason: ... }
});
6. Promise.any()
Promise.any()
方法接收一个 Promise 对象数组作为参数,返回一个新的 Promise 对象。当数组中任意一个 Promise 成功时,返回的 Promise 状态变为 Fulfilled,值为第一个成功的 Promise 的值;当所有 Promise 都失败时,返回的 Promise 状态变为 Rejected,值为一个 AggregateError 对象,包含所有失败的原因。
const promises = [promise1, promise2, promise3];
Promise.any(promises).then(
value => {
// 任意一个 Promise 成功时的处理
},
errors => {
// 所有 Promise 都失败时的处理
console.log(errors); // AggregateError: All promises were rejected
}
);
四、Promise 的错误处理
1. 错误处理方式
Promise 提供了多种错误处理方式:
1.1 使用 then() 的第二个参数
promise.then(
value => {
// 成功处理
},
error => {
// 错误处理
}
);
1.2 使用 catch()
promise
.then(value => {
// 成功处理
})
.catch(error => {
// 错误处理
});
2. then() 与 catch() 的区别
使用 promise.then(onFulfilled, onRejected)
的方式,在 onFulfilled
中发生的异常无法被 onRejected
捕获。而使用 promise.then(onFulfilled).catch(onRejected)
的方式,then
中产生的异常能在 catch
中捕获。
// 不推荐的方式
promise.then(
value => {
throw new Error('then error'); // 这个错误不会被下面的 onRejected 捕获
},
error => {
console.error('onRejected', error);
}
);
// 推荐的方式
promise
.then(value => {
throw new Error('then error'); // 这个错误会被下面的 catch 捕获
})
.catch(error => {
console.error('catch', error);
});
3. 错误冒泡
Promise 链中的错误会沿着链向下传递,直到被捕获。
promise
.then(value => {
throw new Error('error in first then');
})
.then(value => {
console.log('这里不会执行');
})
.catch(error => {
console.error(error); // 捕获上面抛出的错误
return 'recovered';
})
.then(value => {
console.log(value); // 'recovered'
});
4. 未处理的拒绝(Unhandled Rejection)
如果 Promise 被拒绝但没有提供拒绝处理函数,就会发生未处理的拒绝。现代浏览器通常会在控制台中显示警告。
// 这会导致未处理的拒绝警告
Promise.reject(new Error('Rejected Promise'));
// 正确的处理方式
Promise.reject(new Error('Rejected Promise')).catch(error => {
console.error(error);
});
五、Promise 的高级应用
1. Promise 与异步函数的结合
1.1 Promise 与 setTimeout
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
delay(1000).then(() => {
console.log('1秒后执行');
});
1.2 Promise 与 AJAX
function fetchData(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = () => {
if (xhr.status === 200) {
resolve(xhr.responseText);
} else {
reject(new Error(`请求失败:${xhr.status}`));
}
};
xhr.onerror = () => reject(new Error('网络错误'));
xhr.send();
});
}
fetchData('https://api.example.com/data')
.then(data => {
console.log(data);
})
.catch(error => {
console.error(error);
});
2. Promise 的超时处理
function timeoutPromise(promise, ms) {
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(`Promise timed out after ${ms} ms`));
}, ms);
});
return Promise.race([promise, timeoutPromise]);
}
const fetchWithTimeout = timeoutPromise(fetch('https://api.example.com/data'), 5000);
fetchWithTimeout
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
3. Promise 的串行与并行
3.1 串行执行
const tasks = [task1, task2, task3]; // 每个任务都返回 Promise
tasks.reduce((promiseChain, currentTask) => {
return promiseChain.then(chainResults => {
return currentTask().then(currentResult => {
return [...chainResults, currentResult];
});
});
}, Promise.resolve([])).then(results => {
console.log(results); // [task1Result, task2Result, task3Result]
});
3.2 并行执行
const tasks = [task1, task2, task3]; // 每个任务都返回 Promise
Promise.all(tasks.map(task => task()))
.then(results => {
console.log(results); // [task1Result, task2Result, task3Result]
})
.catch(error => {
console.error('至少一个任务失败:', error);
});
4. Promise 的取消
ES6 Promise 本身不支持取消操作,但可以通过一些技巧实现类似的效果:
function createCancelablePromise(promise) {
let isCanceled = false;
const wrappedPromise = new Promise((resolve, reject) => {
promise.then(
value => isCanceled ? reject({ isCanceled: true }) : resolve(value),
error => isCanceled ? reject({ isCanceled: true }) : reject(error)
);
});
return {
promise: wrappedPromise,
cancel: () => { isCanceled = true; }
};
}
const { promise, cancel } = createCancelablePromise(fetch('https://api.example.com/data'));
promise
.then(response => response.json())
.then(data => console.log(data))
.catch(error => {
if (error.isCanceled) {
console.log('Promise was canceled');
} else {
console.error('Error:', error);
}
});
// 在某个时刻取消 Promise
cancel();
六、Promise 与其他异步模式的比较
1. Promise vs 回调函数
回调函数:
- 简单直接,但容易导致回调地狱
- 错误处理分散,每个回调都需要单独处理错误
- 代码可读性差,逻辑不连续
Promise:
- 链式调用,避免回调地狱
- 统一的错误处理机制
- 更好的代码组织和可读性
2. Promise vs async/await
Promise:
- ES6 标准,兼容性更好
- 链式调用,但多个 then 可能影响可读性
- 错误处理需要使用 catch
async/await:
- 基于 Promise 的语法糖,本质上还是使用 Promise
- 使用同步代码的风格编写异步代码,可读性更好
- 可以使用 try/catch 进行错误处理,更接近同步代码
- 更容易实现条件逻辑和循环
// Promise 方式
function fetchData() {
return fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => {
return fetch(`https://api.example.com/related/${data.id}`);
})
.then(response => response.json());
}
// async/await 方式
async function fetchData() {
const response1 = await fetch('https://api.example.com/data');
const data = await response1.json();
const response2 = await fetch(`https://api.example.com/related/${data.id}`);
return response2.json();
}
七、Promise 的实现原理
Promise 的实现遵循 Promises/A+ 规范,核心包括:
- 状态管理:维护 Promise 的三种状态(Pending、Fulfilled、Rejected)
- then 方法:注册回调函数,并返回新的 Promise
- 异步执行:确保回调函数在当前执行栈清空后才被调用
- 值的传递:将 Promise 的值或拒绝原因传递给回调函数
简化的 Promise 实现示例:
class MyPromise {
constructor(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(callback => callback());
}
};
const reject = reason => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(callback => callback());
}
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
const promise2 = new MyPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
}
if (this.state === 'rejected') {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
}
if (this.state === 'pending') {
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
});
}
});
return promise2;
}
catch(onRejected) {
return this.then(null, onRejected);
}
}
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
reject(new TypeError('Chaining cycle detected for promise'));
return;
}
if (x instanceof MyPromise) {
x.then(value => {
resolvePromise(promise2, value, resolve, reject);
}, reject);
} else if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
let called = false;
try {
const then = x.then;
if (typeof then === 'function') {
then.call(x, value => {
if (called) return;
called = true;
resolvePromise(promise2, value, resolve, reject);
}, reason => {
if (called) return;
called = true;
reject(reason);
});
} else {
resolve(x);
}
} catch (error) {
if (called) return;
called = true;
reject(error);
}
} else {
resolve(x);
}
}
八、Promise 的最佳实践
1. 始终返回 Promise
在异步函数中始终返回 Promise,保持一致的接口。
// 不好的做法
function getData(id) {
if (id < 0) {
return null; // 同步返回
}
return fetch(`/api/data/${id}`); // 异步返回 Promise
}
// 好的做法
function getData(id) {
if (id < 0) {
return Promise.reject(new Error('Invalid ID')); // 始终返回 Promise
}
return fetch(`/api/data/${id}`);
}
2. 正确处理错误
始终为 Promise 链添加错误处理。
// 不好的做法
fetch('/api/data')
.then(response => response.json())
.then(data => processData(data));
// 好的做法
fetch('/api/data')
.then(response => response.json())
.then(data => processData(data))
.catch(error => {
console.error('Error:', error);
// 适当的错误处理
});
3. 避免嵌套 Promise
利用 Promise 链而不是嵌套 Promise。
// 不好的做法
fetch('/api/user')
.then(response => response.json())
.then(user => {
fetch(`/api/posts/${user.id}`)
.then(response => response.json())
.then(posts => {
console.log(posts);
});
});
// 好的做法
fetch('/api/user')
.then(response => response.json())
.then(user => fetch(`/api/posts/${user.id}`))
.then(response => response.json())
.then(posts => {
console.log(posts);
})
.catch(error => {
console.error('Error:', error);
});
4. 并行处理多个 Promise
使用 Promise.all()
并行处理多个独立的 Promise。
const userPromise = fetch('/api/user').then(r => r.json());
const postsPromise = fetch('/api/posts').then(r => r.json());
const commentsPromise = fetch('/api/comments').then(r => r.json());
Promise.all([userPromise, postsPromise, commentsPromise])
.then(([user, posts, comments]) => {
// 所有数据都已获取
console.log(user, posts, comments);
})
.catch(error => {
console.error('Error:', error);
});
5. 使用 async/await 简化 Promise 代码
在现代 JavaScript 中,使用 async/await 可以使 Promise 代码更加清晰。
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/user/${userId}`);
const user = await response.json();
const postsResponse = await fetch(`/api/posts/${user.id}`);
const posts = await postsResponse.json();
return { user, posts };
} catch (error) {
console.error('Error:', error);
throw error; // 重新抛出错误以便调用者处理
}
}
// 使用
fetchUserData(123)
.then(data => {
console.log(data);
})
.catch(error => {
// 处理错误
});