Promise是异步编程的核心概念之一。代表一个可能尚未完成的操作,并提供了一种机制来处理该操作最终的成功或失败。具体来说,Promise是由异步函数返回的对象,能够指示该操作当前所处的状态。
当Promise被创建时,它会处于“待定”(Pending)状态,这意味着操作尚未完成。在这个阶段,Promise对象可以通过其提供的方法来注册回调函数,以便在操作最终完成后进行相应的处理。一旦操作完成,Promise的状态会变为“已兑现”(Fulfilled),表示成功;或者变为“已拒绝”(Rejected),表示失败。
除了基本的状态管理,Promise还提供了链式调用的能力,使得开发者可以以更加清晰和可读的方式进行异步操作的组合。例如,可以使用.then()方法处理成功的结果,使用.catch()方法处理错误,甚至可以通过.finally()方法执行一些清理工作,无论操作是成功还是失败。
一、什么是 Promise
Promise 是一个代表异步操作最终完成(或失败)及其结果值的对象。它有三种状态:
- Pending(待定): 初始状态,既不是成功,也不是失败。
- Fulfilled(已兑现): 操作成功完成。
- Rejected(已拒绝): 操作失败。
一旦 Promise 被 fulfilled 或 rejected,它的状态就会被锁定,后续的状态无法再改变。
Promise 的基本构造
Promise是通过 new Promise()
构造函数创建的。这个构造函数接受一个执行器(executor)函数作为参数,该函数接收两个参数:resolve
和 reject
。通过调用 resolve
来标记Promise对象状态为已兑现(fulfilled),而通过调用 reject
则将其标记为已拒绝(rejected)。
const myPromise = new Promise((resolve, reject) => {
// 模拟异步操作
const success = true; // 假设操作成功
if (success) {
resolve('操作成功'); // 标记Promise为已兑现
} else {
reject('操作失败'); // 标记Promise为已拒绝
}
});
Promise 的使用
一旦创建了Promise对象,可以利用 then()
方法处理Promise对象状态为已兑现时的返回值,也可以使用 catch()
方法来处理Promise对象状态为已拒绝时的错误信息。
myPromise
.then(result => {
console.log(result); // 打印: 操作成功
})
.catch(error => {
console.error(error); // 打印错误信息
});
当Promise对象状态为已兑现时,then()
方法会调用传入的回调函数并输出成功的信息;当Promise对象状态为已拒绝时,则会调用catch()
方法中的回调函数输出错误信息。
二、使用 fetch()
API
fetch()
API 是一个现代的网络请求接口,广泛用于发起网络请求并处理响应。它返回一个 Promise,使得异步操作的管理变得更加简单和直观。fetch()
通常用于获取网络资源,如 RESTful API 的数据。
基本的 fetch()
使用
以下是使用 fetch()
发送 GET 请求并处理响应的基本示例:
fetch('https://api.example.com/data')
.then(response => {
// 检查响应是否成功
if (!response.ok) {
throw new Error('网络响应失败');
}
return response.json(); // 解析 JSON 数据
})
.then(data => {
console.log(data); // 输出返回的数据
})
.catch(error => {
console.error('请求失败:', error); // 捕获并输出错误信息
});
-
fetch()
函数向指定的 URL 发送了 GET 请求。 - 响应通过
.then()
方法处理。如果响应不正常(例如状态码不是 200-299),则会抛出一个错误。 - 如果响应成功,使用
response.json()
方法解析 JSON 格式的数据,并在随后的 .then()
中使用解析后的数据。
发送 POST 请求
除了发送 GET 请求外,fetch()
还可以用来发送 POST 请求。在发送 POST 请求时,可以传递一个包含请求体的配置对象。
fetch('https://api.example.com/data', {
method: 'POST', // 指定请求方法为 POST
headers: {
'Content-Type': 'application/json' // 设置请求头部信息
},
body: JSON.stringify({ key: 'value' }) // 转换请求体为 JSON 字符串
})
.then(response => {
// 检查响应状态
if (!response.ok) {
throw new Error('网络响应失败');
}
return response.json(); // 解析 JSON 数据
})
.then(data => {
console.log(data); // 输出返回的数据
})
.catch(error => {
console.error('请求失败:', error); // 捕获并输出错误信息
});
- 使用
method: 'POST'
指明请求类型。 - 设置请求头
Content-Type
为 application/json
,表明请求体的格式。 - 使用
body
属性将请求体转换为 JSON 字符串,以便于服务器理解。
三、链式使用 Promise
Promise 提供了链式调用的能力,这意味着可以在一个 then()
处理程序中返回另一个 Promise,从而形成异步操作的链式结构。
链式调用
下面示例中,展示了如何使用 Promise 的链式调用来依次请求两个不同的数据资源:
fetch('https://api.example.com/data1')
.then(response => response.json())
.then(data1 => {
console.log('数据1:', data1);
return fetch('https://api.example.com/data2'); // 返回另一个 Promise
})
.then(response => response.json())
.then(data2 => {
console.log('数据2:', data2);
})
.catch(error => {
console.error('请求失败:', error);
});
- 第一个
fetch()
请求获取第一个数据资源,通过 .then()
解析响应为 JSON 数据。 - 在第一个
.then()
处理程序中,我们输出第一个数据并返回另一个 fetch()
请求,以发起第二个异步操作。 - 第二个
fetch()
请求获取第二个数据资源,通过 .then()
解析响应为 JSON 数据。 - 最后一个
.then()
处理程序输出第二个数据。
工作原理
当一个 Promise 被 resolve
时,它会传递给下一个 .then()
处理程序。如果在 .then()
处理程序中返回一个新的 Promise,则当前 Promise 的状态将取决于此新 Promise 的状态。这样就形成了一条链,依次处理多个异步操作。
四、错误捕获
在使用 Promise 进行异步操作时,错误处理是非常重要的一部分。通过在 Promise 链中使用 catch()
方法,可以捕获整个链中发生的错误,并进行相应的处理。
错误处理
示例中展示了如何在一个 Promise 链中处理错误:
fetch('https://api.example.com/data1')
.then(response => {
if (!response.ok) {
throw new Error('网络响应失败');
}
return response.json();
})
.then(data1 => {
console.log('数据1:', data1);
return fetch('https://api.example.com/data2');
})
.then(response => {
if (!response.ok) {
throw new Error('网络响应失败');
}
return response.json();
})
.then(data2 => {
console.log('数据2:', data2);
})
.catch(error => {
console.error('请求失败:', error);
});
- 每个
.then()
处理程序都会检查响应是否成功。如果不成功,则抛出一个 Error。 -
catch()
方法用于捕获所有发生的错误,包括前面任何一个 Promise 的错误。 - 如果任何一个 Promise 出现错误,后续的
.then()
处理程序会被跳过,直接执行 catch()
中的错误处理逻辑。
Promise 链中的任何一个 Promise 的错误都会传递到最近的 catch()
方法中。这样做可以确保整个链中的任何一个步骤出现问题时都能得到正确的处理。catch()
方法也可以用来统一处理整个链中的错误,使代码更加清晰和易于维护。
五、Promise 术语
讨论 Promise 中了解一些重要的术语很有帮助。以下是一些常见的 Promise 术语及其含义:
- Promise 实例: 通过
new Promise()
创建的对象,代表一个异步操作的最终完成或失败。 - Executor 函数: Promise 构造函数中传递的函数,定义了异步操作的行为和状态变化。
- then() 方法: 用于处理 Promise 对象的成功状态(fulfilled)的回调函数,接受一个成功的值作为参数。
- catch() 方法: 用于处理 Promise 对象的失败状态(rejected)的回调函数,接受一个错误作为参数。
- finally() 方法: 无论 Promise 的状态如何(成功或失败),都会执行的回调函数。
使用 finally()
下面的示例展示了如何使用 finally()
方法来进行清理工作,无论 Promise 是成功还是失败,finally()
中的回调都会被执行:
fetch('https://api.example.com/data')
.then(response => {
if (!response.ok) {
throw new Error('网络响应失败');
}
return response.json();
})
.then(data => {
console.log(data);
})
.catch(error => {
console.error('请求失败:', error);
})
.finally(() => {
console.log('请求完成,无论成功或失败。');
});
- 如果获取数据成功,数据将会被输出到控制台。
- 如果获取数据失败,错误信息将会被输出到控制台。
- 无论前面的 Promise 是成功还是失败,
finally()
中的回调都会被执行,用来进行一些清理工作或其他必要的操作。
六、合并多个 Promise
处理多个异步操作时,可以使用 Promise.all() 和 Promise.race() 这两种方法来组合多个 Promise 对象。
Promise.all()
Promise.all() 方法接收一个包含多个 Promise 的数组作为参数,只有当所有 Promise 都成功时,返回的 Promise 才会成功。如果其中任何一个 Promise 失败,则返回的 Promise 也会失败。
const promise1 = fetch('https://api.example.com/data1');
const promise2 = fetch('https://api.example.com/data2');
Promise.all([promise1, promise2])
.then(responses => {
return Promise.all(responses.map(response => {
if (!response.ok) {
throw new Error('网络响应失败');
}
return response.json();
}));
})
.then(data => {
console.log('数据:', data);
})
.catch(error => {
console.error('请求失败:', error);
});
- 通过 Promise.all() 组合了两个获取数据的 Promise。
- 如果所有 Promise 都成功,我们会将获取的数据输出到控制台。
- 如果任何一个 Promise 失败(例如网络响应失败),则捕获并输出错误信息。
Promise.race()
Promise.race() 方法返回一个 Promise,该 Promise 只会在第一个 Promise 解决或拒绝时解决。即使其他 Promise 还没有完成,只要有一个 Promise 在之前完成或失败,race() 返回的 Promise 就会立即解决。
const promise1 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, '第一个 Promise 完成');
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(reject, 50, '第二个 Promise 失败');
});
Promise.race([promise1, promise2])
.then(result => {
console.log(result); // 不会执行,因为 promise2 先失败
})
.catch(error => {
console.error(error); // 输出: 第二个 Promise 失败
});
- 创建了两个延迟的 Promise,一个成功一个失败。
- 使用 Promise.race() 来比较这两个 Promise,结果是第二个 Promise 失败,因此 catch() 方法被触发,输出失败的原因。
7. async 和 await
async 和 await 是 ES2017 引入,用于更简洁地处理 Promise。async 关键字用于定义异步函数,await 用于等待 Promise 解决。
使用 async 和 await
async function fetchData() {
try {
const response1 = await fetch('https://api.example.com/data1');
if (!response1.ok) {
throw new Error('网络响应失败');
}
const data1 = await response1.json();
console.log('数据1:', data1);
const response2 = await fetch('https://api.example.com/data2');
if (!response2.ok) {
throw new Error('网络响应失败');
}
const data2 = await response2.json();
console.log('数据2:', data2);
} catch (error) {
console.error('请求失败:', error);
}
}
fetchData();
- 使用 async 定义了一个异步函数 fetchData(),里面包含多个 await 表达式来等待 Promise 解决,并处理返回的数据。
- 如果任何一个 Promise 解决失败,错误信息将被捕获并输出到控制台。
async 函数的返回值
async 函数总是返回一个 Promise,即使函数内没有显式返回值。下面的示例展示了这一点:
async function example() {
return 'Hello, World!';
}
example().then(message => {
console.log(message); // 输出: Hello, World!
});
- async 函数 example() 返回一个字符串 'Hello, World!'。
- 虽然在函数内没有显式返回 Promise,但由于是 async 函数,最终返回的仍然是一个 Promise 对象。