在JavaScript的异步编程领域,Promise无疑是一颗闪耀的明星。它为处理那些麻烦的延迟操作提供了一种简洁而高效的解决方案。
1 Promise的基本概念
首先,我们得明白Promise是个啥。简单来说,Promise就是一个代表了尚未完成但预计在未来会完成的操作的容器。它非常类似于一个你会在日后打开的盒子,盒子里可能是你想要的答案,也可能是个坏消息。
创建一个Promise就像制作这样一个盒子。你用new Promise来制作,里面填上你的异步操作,比如发起一个网络请求或者读取一个文件。
Promise有三种状态:pending(待定)、fulfilled(实现)和rejected(拒绝)。一开始,Promise是pending,表示操作还未完成。一旦操作成功,状态就会变成fulfilled;如果出了岔子,状态就会变成rejected。而且,一旦状态改变,就没法再变回去了。
2 使用.then()和.catch()方法
在JavaScript中,.then()和.catch()是Promise对象的方法,用于处理异步操作的结果。
- .then()方法:当Promise对象的状态变为fulfilled(已实现)时,会调用.then()方法中的回调函数,并将异步操作的结果作为参数传递给回调函数。.then()方法返回一个新的Promise对象,可以链式调用多个.then()方法来处理不同的结果。
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('成功');
}, 1000);
});
promise.then((result) => {
console.log(result); // 输出 "成功"
}).then((result) => {
console.log('第二个 then');
});
- .catch()方法:当Promise对象的状态变为rejected(已拒绝)时,会调用.catch()方法中的回调函数,并将错误信息作为参数传递给回调函数。.catch()方法也可以链式调用,用于捕获前面所有.then()中抛出的错误。
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
reject('失败');
}, 1000);
});
promise.then((result) => {
console.log(result);
}).catch((error) => {
console.log(error); // 输出 "失败"
});
如果Promise对象的状态已经是fulfilled或rejected,那么后续的.then()和.catch()将不会执行。另外,如果在.then()中抛出了异常,那么这个异常会被后面的.catch()捕获。
3 并发执行和Promise的静态方法
有时候,你需要同时运行多个Promise。这时,Promise.all()和Promise.race()就派上了用场。Promise.all()会等待所有的Promise都完成,然后才继续。而Promise.race()则不那么耐心,只要有一个Promise完成,不管是fulfilled还是rejected,它就会立即继续。
- Promise.all(iterable): 这个方法接收一个包含多个Promise对象的可迭代对象(如数组),并返回一个新的Promise对象。这个新的Promise对象会在所有传入的Promise对象都成功完成时变为fulfilled状态,并将每个Promise的结果组成一个数组作为参数传递给回调函数。如果任何一个Promise失败,那么新的Promise对象会立即变为rejected状态,并将第一个失败的原因作为参数传递给回调函数。
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(resolve, 100, 'foo'));
const promise3 = Promise.resolve(5);
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values); // 输出 [3, "foo", 5]
}).catch((error) => {
console.log(error);
});
- Promise.race(iterable): 这个方法也接收一个包含多个Promise对象的可迭代对象,但与Promise.all()不同,它返回一个新的Promise对象,该对象会在传入的Promise对象中的任意一个完成或失败时立即改变状态。无论哪个Promise先完成或失败,都会将相应的结果或原因传递给回调函数。
const promise1 = new Promise((resolve, reject) => setTimeout(resolve, 500, 'one'));
const promise2 = new Promise((resolve, reject) => setTimeout(resolve, 100, 'two'));
Promise.race([promise1, promise2]).then((value) => {
console.log(value); // 输出 "two"
}).catch((error) => {
console.log(error);
});
Promise.all()和Promise.race()都是异步操作,它们不会阻塞代码的执行。这意味着你可以在等待这些Promise完成的同时继续执行其他任务。
4 高级技巧:Promise.prototype.finally()
Promise.prototype.finally()是Promise原型上的一个方法,它用于在Promise链的末尾添加一个最终处理程序,无论Promise的状态是fulfilled还是rejected,这个处理程序都会被调用。这就像是不管你去参加派对后心情如何,回家总是要做的。
4.1 作用
- finally()方法的主要作用是在Promise链的最后添加清理或收尾工作,比如关闭文件、释放资源、清除定时器等。
- 这个方法接受一个回调函数作为参数,这个回调函数没有参数,也不返回值。
4.2 用法
const promise = new Promise((resolve, reject) => {
// ...异步操作
});
promise
.then(result => {
// ...处理成功结果
})
.catch(error => {
// ...处理错误
})
.finally(() => {
// ...执行清理或收尾工作
});
4.3 特点
- 始终执行:无论前面的then()或catch()是否抛出异常,finally()中的回调函数都会被执行。
- 无参数:finally()中的回调函数不接收任何参数,这意味着它不能直接访问到then()或catch()中的结果或错误。
- 返回新的Promise:如果finally()中的回调函数抛出异常,或者返回一个新的Promise,那么这个新的Promise会成为finally()方法的返回值。
4.4 技巧
- 链式调用:finally()可以与其他Promise方法进行链式调用。
- 错误处理:在finally()中可以进行一些通用的错误处理,比如记录日志、发送错误报告等。
- 资源清理:finally()非常适合用来执行一些无论成功还是失败都需要进行的资源清理工作。
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => {
console.log(data);
return data;
})
.catch(error => {
console.error('Error:', error);
})
.finally(() => {
console.log('Fetch completed');
});
上述代码中,无论fetch()请求成功还是失败,都会在控制台输出"Fetch completed"。
5 ES6的async/await
在ES6中,async/await是一种处理异步操作的新方法,它基于Promise实现,但提供了更加简洁和直观的语法。
- async关键字:用于声明一个函数为异步函数。一个被async修饰的函数会自动将返回值包装成一个Promise对象。如果返回值是thenable对象,那么会等待其解析为最终值;如果返回值是一个原始值,那么会直接解析为该值。
async function asyncFunc() {
return 'hello';
}
const result = asyncFunc(); // result是一个Promise对象
result.then(console.log); // 输出 "hello"
- await关键字:用于在async函数内部等待一个Promise解析为最终值。await暂停代码执行,直到Promise解析完成,然后返回解析值。如果Promise被拒绝,那么await会抛出异常。
async function asyncFunc() {
const result = await new Promise((resolve, reject) => setTimeout(resolve, 1000, 'world'));
console.log(result); // 1秒后输出 "world"
}
asyncFunc();
使用async/await的优势:
- 更简洁:不需要使用.then()链或.catch()来处理结果和错误。
- 更好的错误处理:可以使用正常的try/catch语句来捕获异常。
- 顺序执行:可以更容易地保证异步操作按预期的顺序执行。
async function fetchData(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
console.log(data);
} catch (error) {
console.error('There has been a problem with your fetch operation:', error);
}
}
fetchData('https://api.example.com/data');
我们定义了一个异步函数fetchData,它接受一个URL作为参数。我们使用await来等待fetch()请求的结果,然后再等待将响应体解析为JSON。如果在这个过程中发生任何错误,我们可以使用try/catch语句来捕获并处理它。