JavaScript中的Promise是一种异步编程的解决方案,它代表一个尚未完成但预期将来会完成的操作。与传统的回调函数相比,Promise提供了更优雅的异步处理方式。
每个Promise对象都有三种可能的状态:
状态一旦改变就不可逆转,只能从Pending变为Fulfilled或Rejected。
创建一个Promise对象的基本语法如下:
javascript复制const promise = new Promise((resolve, reject) => {
// 异步操作
if (/* 操作成功 */) {
resolve(value); // 将Promise状态改为fulfilled
} else {
reject(error); // 将Promise状态改为rejected
}
});
then()方法用于为Promise实例添加状态改变时的回调函数:
javascript复制promise.then(
function(value) {
// 成功时的处理
},
function(error) {
// 失败时的处理(可选)
}
);
注意:then()方法返回的是一个新的Promise对象,因此可以链式调用。
catch()方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数:
javascript复制promise.catch(function(error) {
console.error('出错了', error);
});
finally()方法不管Promise对象最后状态如何都会执行:
javascript复制promise.finally(() => {
// 无论成功失败都会执行
});
Promise的then方法返回一个新的Promise,这使得链式调用成为可能:
javascript复制doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.catch(error => console.error(error));
Promise.all()方法用于将多个Promise实例包装成一个新的Promise实例:
javascript复制const p = Promise.all([p1, p2, p3]);
Promise.race()方法同样是将多个Promise实例包装成一个新的Promise实例:
javascript复制const p = Promise.race([p1, p2, p3]);
Promise通过链式调用解决了传统回调函数嵌套过深的问题:
javascript复制// 回调地狱
doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doThirdThing(newResult, function(finalResult) {
console.log('最终结果: ' + finalResult);
}, failureCallback);
}, failureCallback);
}, failureCallback);
// Promise解决方案
doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => console.log('最终结果: ' + finalResult))
.catch(failureCallback);
Promise的错误处理有几种常见模式:
建议:在Promise链的末尾添加一个catch()来处理整个链中可能出现的任何错误。
虽然Promise提供了更好的异步处理方式,但也需要注意:
原生Promise不支持取消操作,但可以通过以下方式实现:
javascript复制function makeCancelablePromise(promise) {
let hasCanceled = false;
const wrappedPromise = new Promise((resolve, reject) => {
promise.then(
val => hasCanceled ? reject({isCanceled: true}) : resolve(val),
error => hasCanceled ? reject({isCanceled: true}) : reject(error)
);
});
return {
promise: wrappedPromise,
cancel() {
hasCanceled = true;
}
};
}
原生Promise不提供进度通知功能,但可以扩展实现:
javascript复制function ProgressPromise(executor) {
const handlers = [];
this.then = function(onFulfilled, onRejected, onProgress) {
handlers.push({onFulfilled, onRejected, onProgress});
return this;
};
function notify(progress) {
handlers.forEach(handler => {
if (typeof handler.onProgress === 'function') {
handler.onProgress(progress);
}
});
}
executor(
value => handlers.forEach(handler => handler.onFulfilled(value)),
reason => handlers.forEach(handler => handler.onRejected(reason)),
notify
);
}
Promise可以与生成器函数结合,实现更简洁的异步代码:
javascript复制function runGenerator(generatorFunc) {
const generator = generatorFunc();
function handle(result) {
if (result.done) return Promise.resolve(result.value);
return Promise.resolve(result.value)
.then(res => handle(generator.next(res)))
.catch(err => handle(generator.throw(err)));
}
return handle(generator.next());
}
使用Promise封装AJAX请求:
javascript复制function fetchData(url, method = 'GET', data = null) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response);
} else {
reject(new Error(xhr.statusText));
}
};
xhr.onerror = () => reject(new Error('Network Error'));
xhr.send(data);
});
}
将图片加载转换为Promise:
javascript复制function loadImage(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = () => reject(new Error(`加载失败: ${url}`));
img.src = url;
});
}
使用Promise实现多个异步操作的顺序执行:
javascript复制function executeSequentially(promiseFactories) {
let result = Promise.resolve();
const results = [];
promiseFactories.forEach(factory => {
result = result.then(() => factory()).then(res => results.push(res));
});
return result.then(() => results);
}
虽然Promise解决了回调地狱,但不正确使用仍会导致Promise嵌套:
javascript复制// 错误的嵌套写法
doSomething().then(result1 => {
doSomethingElse(result1).then(result2 => {
doThirdThing(result2).then(result3 => {
console.log(result3);
});
});
});
// 正确的链式写法
doSomething()
.then(result1 => doSomethingElse(result1))
.then(result2 => doThirdThing(result2))
.then(result3 => console.log(result3));
在then()回调中如果忘记返回Promise,会导致链式调用中断:
javascript复制// 错误示例
doSomething()
.then(result => {
doSomethingElse(result); // 忘记return
})
.then(newResult => {
// newResult将是undefined
});
// 正确示例
doSomething()
.then(result => {
return doSomethingElse(result); // 显式返回
})
.then(newResult => {
// 可以正常获取newResult
});
错误处理的位置会影响捕获的范围:
javascript复制// 只能捕获doSomething的错误
doSomething()
.catch(error => console.error(error))
.then(result => doSomethingElse(result));
// 可以捕获整个链中的错误
doSomething()
.then(result => doSomethingElse(result))
.catch(error => console.error(error));
async函数本质上返回一个Promise:
javascript复制async function foo() {
return 'hello';
}
// 等同于
function foo() {
return Promise.resolve('hello');
}
await关键字可以等待一个Promise解决:
javascript复制async function bar() {
const result = await somePromise();
console.log(result);
}
async/await可以使用try/catch处理错误:
javascript复制async function baz() {
try {
const result = await somePromise();
console.log(result);
} catch (error) {
console.error(error);
}
}
在不需要异步操作时,避免创建Promise:
javascript复制// 不推荐
function getDataSync() {
return new Promise(resolve => {
resolve(syncOperation());
});
}
// 推荐
function getDataSync() {
return Promise.resolve(syncOperation());
}
对于相同参数的异步操作,可以使用缓存:
javascript复制const cache = new Map();
function cachedFetch(url) {
if (cache.has(url)) {
return cache.get(url);
}
const promise = fetch(url).then(res => res.json());
cache.set(url, promise);
return promise;
}
对于大量Promise操作,考虑批量处理:
javascript复制async function processInBatches(items, batchSize, processItem) {
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
await Promise.all(batch.map(processItem));
}
}
使用测试框架测试Promise:
javascript复制// 使用Jest测试Promise
test('测试异步函数', () => {
return fetchData().then(data => {
expect(data).toBeDefined();
});
});
// 或者使用async/await
test('测试异步函数', async () => {
const data = await fetchData();
expect(data).toBeDefined();
});
调试Promise时可以使用以下技巧:
监听未处理的Promise拒绝:
javascript复制process.on('unhandledRejection', (reason, promise) => {
console.error('未处理的Promise拒绝:', reason);
});
在实际项目中,Promise已经成为处理异步操作的标准方式。掌握Promise的各种用法和技巧,能够显著提高JavaScript异步代码的可读性和可维护性。从基础用法到高级技巧,Promise提供了丰富的功能来处理各种异步场景。