Promise 是 JavaScript 中用于处理异步操作的一种重要机制。Promise 用于解决 JavaScript 中异步操作的复杂性,通过状态管理、链式调用、错误处理等功能,实现代码的清晰、有序与可维护,避免回调地狱,提升异步编程的效率与体验。
一、Promise 核心机制
Promise 是 JavaScript 中用于处理异步操作的一种重要机制,Promise 是 JavaScript 中用于处理异步操作的对象,提供统一、链式调用的 API,简化异步编程,避免回调地狱。具有 Pending(进行中)、Fulfilled(已完成)和 Rejected(已失败)三种状态,通过 .then()
、.catch()
、.finally()
等方法注册回调,支持 async/await
语法。其主要作用包括:
1、解决回调地狱
Promise 通过链式调用(.then()
和 .catch()
)替代了传统的回调函数嵌套,使得异步代码逻辑更加清晰、扁平,避免了“回调地狱”带来的代码可读性和可维护性问题。
// 回调地狱示例
doSomethingAsync(function(result1) {doAnotherThingAsync(result1, function(result2) {yetAnotherAsync(result2, function(finalResult) {console.log(finalResult);});});
});// 使用 Promise 的链式调用
doSomethingAsync().then(doAnotherThingAsync).then(yetAnotherAsync).then(finalResult => console.log(finalResult)).catch(handleError);
2、统一异步编程接口
Promise 提供了一种通用的异步编程模型,无论是处理网络请求、文件操作、定时器还是其他异步任务,都可以使用相同的 Promise API 进行封装和处理。这促进了代码的模块化和复用,同时也方便了异步代码的测试和调试。
以下是一些使用 Promise 统一异步编程接口的示例:
网络请求示例:
// 使用 fetch API 发送 GET 请求并返回 Promise
function fetchUser(userId) {return fetch(`https://api.example.com/users/${userId}`).then(response => {if (!response.ok) {throw new Error('Network response was not ok');}return response.json(); // 解析 JSON 数据}).catch(error => {console.error('Error fetching user:', error);throw error; // 抛出错误以供上层处理});
}fetchUser(123).then(user => console.log('Fetched user:', user)).catch(error => console.error('Error fetching user:', error));
在这个示例中,fetchUser
函数封装了一个网络请求,返回一个 Promise。无论外部如何调用这个函数,都可以通过 .then()
和 .catch()
方法处理异步结果和错误。
文件读写示例:
const fs = require('fs').promises;function readFileAsync(filePath) {return fs.readFile(filePath, 'utf8').catch(error => {console.error('Error reading file:', error);throw error;});
}function writeFileAsync(filePath, content) {return fs.writeFile(filePath, content, 'utf8').catch(error => {console.error('Error writing file:', error);throw error;});
}readFileAsync('input.txt').then(content => {console.log('Read content:', content);return writeFileAsync('output.txt', content.toUpperCase());}).then(() => console.log('Content written to output.txt')).catch(error => console.error('Error reading or writing file:', error));
这里,readFileAsync
和 writeFileAsync
函数分别封装了文件读取和写入操作,返回 Promise。尽管底层操作涉及文件系统,但外部调用时依然可以通过 Promise 的链式调用来处理异步结果和错误,实现了异步操作的统一接口。
定时器示例:
function delay(ms) {return new Promise(resolve => setTimeout(resolve, ms));
}delay(2000).then(() => console.log('2 seconds have passed')).catch(error => console.error('Error with delay:', error));
尽管 JavaScript 的 setTimeout
函数本身不返回 Promise,但我们可以通过封装一个 delay
函数,使其返回 Promise,这样就可以使用 Promise 的接口来处理定时器结束后的操作。
这些示例展示了 Promise 如何作为统一的异步编程接口,无论底层异步操作的具体类型如何,都可通过 Promise 的 .then()
、.catch()
等方法进行统一的异步流程控制,提高了代码的可读性和可维护性。
3、 状态管理与错误传播
Promise 对象具有 Pending(进行中)、Fulfilled(已完成)和 Rejected(已失败)三种状态,状态一旦确定便不可更改。这种状态机模型使得异步操作的状态易于跟踪和理解。同时,Promise 通过 .catch()
方法可以集中处理异步操作中的错误,简化了错误传播和处理机制。
Promise 状态管理指的是 Promise 对象在其生命周期中有且只能处于三种状态之一:Pending(进行中)、Fulfilled(已成功)或 Rejected(已失败)。一旦 Promise 进入 Fulfilled 或 Rejected 状态,其状态就永久不变。这种状态管理机制使得 Promise 可以清晰地表示异步操作的结果。错误传播则是指 Promise 中的错误可以在 Promise 链中向上冒泡,直到被捕获处理。以下是一些关于 Promise 状态管理和错误传播的示例:
状态管理示例
const myPromise = new Promise((resolve, reject) => {setTimeout(() => {// 模拟异步操作成功resolve('Async operation succeeded!');}, 1000);
});myPromise.then(result => {console.log('Promise resolved:', result);
});// 一段时间后...
console.log('Promise status:', myPromise); // 输出:Promise { <pending> }// 1秒后(异步操作完成)
// 输出:Promise resolved: Async operation succeeded!
// 输出:Promise status: Promise { <fulfilled>: "Async operation succeeded!" }
在这个示例中,我们创建了一个 Promise 对象 myPromise
,并在异步操作成功时调用 resolve
函数。在异步操作完成之前,myPromise
的状态为 Pending。当异步操作完成并调用 resolve
后,myPromise
的状态变为 Fulfilled,且其结果可以在 .then()
方法中访问。通过打印 myPromise
本身,可以看到其状态的变化。
错误传播示例
const failingPromise = new Promise((resolve, reject) => {setTimeout(() => {// 模拟异步操作失败reject(new Error('Async operation failed!'));}, 1000);
});failingPromise.then(result => {console.log('Promise resolved:', result);}).catch(error => {console.error('Promise rejected:', error);});// 一段时间后...
console.log('Promise status:', failingPromise); // 输出:Promise { <pending> }// 1秒后(异步操作完成)
// 输出:Promise rejected: Error: Async operation failed!
// 输出:Promise status: Promise { <rejected>: Error: Async operation failed! }
在这个示例中,我们创建了一个会在异步操作失败时调用 reject
函数的 Promise 对象 failingPromise
。当异步操作失败并调用 reject
后,failingPromise
的状态变为 Rejected,且其错误原因可以在 .catch()
方法中捕获。如果没有 .catch()
处理错误,这个错误会沿着 Promise 链向上冒泡,直到被捕获或导致全局未捕获错误。通过打印 failingPromise
本身,可以看到其状态的变化。
链式调用与错误传播示例
const firstPromise = Promise.resolve('First step succeeded');
const secondPromise = firstPromise.then(() => {throw new Error('Second step failed');
});secondPromise.then(result => {console.log('Second step succeeded:', result);}).catch(error => {console.error('Second step failed:', error);});// 输出:Second step failed: Error: Second step failed
在这个示例中,我们创建了一个初始状态为 Fulfilled 的 Promise firstPromise
。接着,我们通过 .then()
方法创建了一个新的 Promise secondPromise
,并在其回调函数中抛出了一个错误。由于抛出的错误,secondPromise
的状态变为 Rejected。在链式调用的 .catch()
方法中,我们捕获到了这个错误,并进行了相应的处理。即使 firstPromise
成功,后续链式调用中出现的错误也会沿着链向上传播,直到被捕获。
4、支持并发与顺序控制
Promise 提供了诸如 Promise.all()
、Promise.race()
和 Promise.any()
等方法,用于控制多个 Promise 的并发执行或顺序执行,以及根据它们的状态进行相应的逻辑处理。
Promise 提供了一些方法来实现对多个异步操作的并发与顺序控制。以下是一些具体的示例:
4.1、并发控制示例:Promise.all
// 定义三个异步操作
const fetchUser1 = fetch('https://api.example.com/users/1');
const fetchUser2 = fetch('https://api.example.com/users/2');
const fetchUser3 = fetch('https://api.example.com/users/3');// 使用 Promise.all 进行并发请求
Promise.all([fetchUser1, fetchUser2, fetchUser3]).then(responses => Promise.all(responses.map(r => r.json()))) // 解析所有响应为 JSON.then(users => console.log('Fetched all users:', users)).catch(error => console.error('Error fetching users:', error));
在这个示例中,Promise.all
接收一个 Promise 数组作为参数。当所有 Promise 都成功时,Promise.all
返回的 Promise 解析为一个包含所有结果的数组。如果有任何一个 Promise 失败,Promise.all
返回的 Promise 会立即变为 rejected 状态,传递第一个 rejection 的原因。
顺序控制示例:链式调用
fetch('https://api.example.com/users/1').then(response => response.json()).then(user1 => {console.log('Fetched user 1:', user1);return fetch('https://api.example.com/users/2'); // 依赖 user1 的结果发起下一个请求}).then(response => response.json()).then(user2 => {console.log('Fetched user 2:', user2);return fetch('https://api.example.com/users/3'); // 依赖 user2 的结果发起下一个请求}).then(response => response.json()).then(user3 => console.log('Fetched user 3:', user3)).catch(error => console.error('Error fetching users:', error));
在这个示例中,通过链式调用 .then()
方法,实现了异步操作的顺序执行。每个请求依赖于前一个请求的结果,只有当前一个请求成功并返回结果后,才会发起下一个请求。如果有任何一个请求失败,后续的 .then()
将不会执行,而是跳转到 .catch()
处理错误。
并发控制与顺序控制结合示例:Promise.all 与链式调用
const fetchUserIds = [1, 2, 3];let fetchedUsers = [];fetchUserIds.forEach(id => {fetch(`https://api.example.com/users/${id}`).then(response => response.json()).then(user => {fetchedUsers.push(user);if (fetchedUsers.length === fetchUserIds.length) { // 所有用户都已获取console.log('Fetched all users:', fetchedUsers);}}).catch(error => console.error('Error fetching user:', error));
});// 或者使用 Promise.allSettled,不因个别请求失败而终止其他请求的执行
Promise.allSettled(fetchUserIds.map(id => fetch(`https://api.example.com/users/${id}`).then(r => r.json()))).then(results => {const fetchedUsers = results.filter(r => r.status === 'fulfilled').map(r => r.value);console.log('Fetched all users:', fetchedUsers);}).catch(error => console.error('Error fetching users:', error));
4.2、并发控制示例:Promise.race()
Promise.race()
方法用于并发执行一组 Promise,并返回一个新 Promise。这个新 Promise 在竞赛中的任何一个 Promise 解决(resolve)或拒绝(reject)后,就会立即以相同的解决值或拒绝原因结束。以下是 Promise.race()
的几个示例:
示例 1:超时控制
function fetchWithTimeout(url, timeoutMs) {const fetchPromise = fetch(url);const timeoutPromise = new Promise((_, reject) => {setTimeout(() => reject(new Error('Request timed out')), timeoutMs);});return Promise.race([fetchPromise, timeoutPromise]);
}fetchWithTimeout('https://api.example.com/data', 5000).then(response => response.json()).then(data => console.log('Fetched data:', data)).catch(error => {if (error.message === 'Request timed out') {console.error('Request timed out');} else {console.error('Error fetching data:', error);}});
在这个示例中,我们创建了一个 fetchWithTimeout
函数,它同时启动一个网络请求(fetchPromise
)和一个定时器(timeoutPromise
)。Promise.race()
方法使得当两者中任意一个首先完成(无论是网络请求成功还是定时器触发)时,返回的 Promise 就会结束。这样,我们就可以在 5 秒内超时的情况下取消长时间未响应的网络请求。
示例 2:竞速登录
const localLoginPromise = attemptLocalLogin();
const socialLoginPromise = attemptSocialLogin();Promise.race([localLoginPromise, socialLoginPromise]).then(loginResult => {console.log('User logged in successfully:', loginResult);// 继续后续操作,如跳转到主页面}).catch(error => {console.error('Login failed:', error);// 显示错误信息或提示用户尝试其他登录方式});
在这个示例中,用户可以选择本地账户登录(localLoginPromise
)或通过社交平台登录(socialLoginPromise
)。Promise.race()
使得用户可以使用最先完成登录的方式,无论哪个方式先成功,都会立即结束登录流程并继续后续操作。如果有任何一方登录失败,catch
块将捕获并处理错误。
示例 3:资源加载优先级
const highPriorityResource = fetchHighPriorityData();
const lowPriorityResource = fetchLowPriorityData();Promise.race([highPriorityResource, lowPriorityResource]).then(resource => {// 加载完成的资源可能是高优先级或低优先级的console.log('Resource loaded:', resource);// 使用已加载的资源,如渲染到页面}).catch(error => {console.error('Error loading resource:', error);// 处理加载失败的情况});
在这个示例中,我们同时加载高优先级和低优先级的资源。Promise.race()
确保了优先加载完成的资源会被立即使用,而不必等待另一个资源加载完成。这样可以优化页面加载速度,尽早呈现可用内容给用户。如果有任何一个资源加载失败,catch
块将捕获并处理错误。
以上示例展示了 Promise.race()
在超时控制、竞速执行、资源加载优先级等方面的实用场景,它允许你根据多个 Promise 中最快完成的那个来决定程序的下一步行动。
4.3、并发控制示例:Promise.all
Promise.any()
方法用于并发执行一组 Promise,并返回一个新 Promise。这个新 Promise 在竞赛中的任何一个 Promise 成功解决(resolve)后,就会立即以该解决值结束。如果所有 Promise 都拒绝(reject),那么返回的 Promise 也会以 AggregateError(包含所有拒绝原因的聚合错误)拒绝。以下是 Promise.any()
的几个示例:
示例 1:多源数据获取,只要有一个成功即可
const dataSources = [fetch('https://api.source1.com/data'),fetch('https://api.source2.com/data'),fetch('https://api.source3.com/data'),
];Promise.any(dataSources.map(source => source.then(response => response.json()))).then(data => {console.log('Fetched data from at least one source:', data);// 使用获取到的数据进行后续操作}).catch(error => {if (error instanceof AggregateError) {console.error('All data sources failed:', error.errors);} else {console.error('Unexpected error:', error);}});
在这个示例中,我们尝试从三个不同的数据源获取数据。只要其中一个数据源成功返回数据,Promise.any()
返回的 Promise 就会解析为该数据,并停止尝试其他源。如果所有数据源都失败,catch
块将捕获一个 AggregateError,其中包含了所有失败请求的错误信息。
示例 2:备用服务选择
const primaryService = callPrimaryService();
const backupService = callBackupService();Promise.any([primaryService, backupService]).then(result => {console.log('Successfully called either the primary or backup service:', result);// 使用返回的结果进行后续操作}).catch(error => {if (error instanceof AggregateError) {console.error('Both primary and backup services failed:', error.errors);} else {console.error('Unexpected error:', error);}});
在这个示例中,我们尝试调用主服务和备选服务。只要其中一个服务成功响应,Promise.any()
返回的 Promise 就会解析为该服务的响应结果,并停止尝试另一个服务。如果两个服务都失败,catch
块将捕获一个 AggregateError,包含两个服务失败的错误信息。
示例 3:并发任务执行,至少一个成功即可
const tasks = [performTaskA(),performTaskB(),performTaskC(),
];Promise.any(tasks).then(successfulResult => {console.log('At least one task completed successfully:', successfulResult);// 使用成功的任务结果进行后续操作}).catch(error => {if (error instanceof AggregateError) {console.error('All tasks failed:', error.errors);} else {console.error('Unexpected error:', error);}});
在这个示例中,我们并发执行多个任务。只要其中一个任务成功完成,Promise.any()
返回的 Promise 就会解析为该任务的成功结果,并忽略其他任务的结果。如果所有任务都失败,catch
块将捕获一个 AggregateError,包含所有任务失败的错误信息。
以上示例展示了 Promise.any()
在多源数据获取、备用服务选择、并发任务执行等场景中的应用,它允许你在一组 Promise 中只要有一个成功就继续执行后续逻辑,降低了对单一资源或服务的依赖性。当所有 Promise 都失败时,通过 AggregateError 可以一次性查看所有失败原因。
在这个示例中,我们并发地发起多个请求,但通过数组 fetchedUsers
和检查其长度来确保所有请求完成后才打印结果,实现了并发请求与顺序控制的结合。另一种做法是使用 Promise.allSettled
,它允许所有请求并发执行,即使有个别请求失败也不会导致整个操作终止,最后收集所有请求的结果(不论成功或失败),根据结果状态进行相应的处理。
5、与 async/await 结合
Promise 是 async/await
语法的基础。async
函数返回一个 Promise,await
关键字可以用于等待 Promise 解决,使得异步代码可以用看起来像同步的方式编写,进一步提升了代码的可读性和简洁性。
Promise
与 async/await
是 JavaScript 中异步编程的两种紧密相关的方式。async/await
是基于 Promise
实现的,它提供了一种更简洁、更易读的同步风格的语法来编写异步代码。以下是如何结合使用 Promise
与 async/await
的示例:
使用 async 函数
async
关键字用于声明一个异步函数。async
函数总是返回一个 Promise,即使没有明确地 return
一个 Promise。在 async
函数内部,可以使用 await
关键字来等待 Promise 解决:
// 声明一个 async 函数
async function fetchData() {try {const response = await fetch('https://api.example.com/data'); // 等待 fetch 操作完成const data = await response.json(); // 等待响应体解析为 JSONreturn data; // 返回解析后的数据,作为 Promise 的 resolve 值} catch (error) {// 如果在 await 表达式中遇到错误,会捕获并进入此 catch 块console.error('Error fetching data:', error);throw error; // 抛出错误,作为 Promise 的 reject 值}
}// 使用 async 函数
fetchData().then(data => {console.log('Fetched data:', data);}).catch(error => {console.error('Error fetching data:', error);});
在这个示例中,我们声明了一个 async
函数 fetchData
,它内部使用 await
关键字等待 fetch
操作和 JSON 解析完成。fetchData
函数返回一个 Promise,其结果取决于 fetch
和 json
操作的成功与否。外部代码通过 .then()
和 .catch()
方法处理这个 Promise,就像处理普通 Promise 一样。
使用 async/await 处理 Promise 链
async/await
可以轻松地处理复杂的 Promise 链,避免深层嵌套的 .then()
和 .catch()
:
async function processData() {try {const response1 = await fetch('https://api.example.com/data1');const data1 = await response1.json();const response2 = await fetch(`https://api.example.com/data2?param=${data1.id}`);const data2 = await response2.json();const response3 = await fetch(`https://api.example.com/data3?param=${data2.relatedId}`);const data3 = await response3.json();return [data1, data2, data3];} catch (error) {console.error('Error processing data:', error);throw error;}
}processData().then(([data1, data2, data3]) => {console.log('Processed data:', data1, data2, data3);}).catch(error => {console.error('Error processing data:', error);});
在这个示例中,processData
函数使用 async/await
依次执行三个异步操作,并返回一个数组包含所有结果。外部代码仍然通过 .then()
和 .catch()
处理返回的 Promise。虽然 processData
内部使用了多个 await
,但在外部看来,它只是一个返回 Promise 的普通函数。
结合使用 Promise.all() 与 async/await
Promise.all()
与 async/await
结合使用,可以并行处理多个 Promise,并等待所有 Promise 完成:
async function fetchMultipleData(urls) {try {const responses = await Promise.all(urls.map(url => fetch(url)));const data = await Promise.all(responses.map(r => r.json()));return data;} catch (error) {console.error('Error fetching multiple data:', error);throw error;}
}const urls = ['https://api.example.com/data1', 'https://api.example.com/data2', 'https://api.example.com/data3'];fetchMultipleData(urls).then(data => {console.log('Fetched multiple data:', data);}).catch(error => {console.error('Error fetching multiple data:', error);});
在这个示例中,fetchMultipleData
函数使用 Promise.all()
并行发起多个 fetch
请求,然后等待所有响应体解析为 JSON。整个过程使用 async/await
语法,使得代码更易读。外部代码同样通过 .then()
和 .catch()
处理返回的 Promise。
综上所述,Promise
与 async/await
可以很好地结合使用,async/await
提供了一种更符合同步编程思维的语法来编写异步代码,而 Promise
作为基础,提供了异步操作的封装和处理机制。二者结合,使得异步编程既简洁又强大。
6、提升代码的可测试性
由于 Promise 提供了清晰的链式调用和错误处理机制,使得异步代码更易于编写单元测试,可以使用断言库对 Promise 的状态和返回值进行验证。
二、Promise创建与使用
1、创建 Promise
Promise 通常通过构造函数 new Promise(executor)
来创建。executor
是一个带有两个参数(resolve
和 reject
)的函数,用于定义异步操作的执行逻辑:
const myPromise = new Promise((resolve, reject) => {// 异步操作逻辑// 成功时调用 resolve(value)// 失败时调用 reject(reason)
});
在 executor
函数中,你需要执行异步操作,并在操作成功时调用 resolve(value)
,将 Promise 状态设置为 Fulfilled(已成功),并将结果 value
传递给后续的 .then()
方法。在操作失败时调用 reject(reason)
,将 Promise 状态设置为 Rejected(已失败),并将错误原因 reason
传递给后续的 .catch()
或 .then()
方法。
2、使用 Promise
Promise 主要通过 .then()
、.catch()
、.finally()
等方法来使用,这些方法允许你指定异步操作成功、失败或完成时的回调函数:
myPromise.then(result => {// 处理成功结果console.log('Promise resolved:', result);return transformedResult; // 可选,返回新的值供后续 then 使用}).catch(error => {// 处理失败原因console.error('Promise rejected:', error);throw new Error('Re-thrown error'); // 可选,重新抛出错误供后续 catch 使用}).finally(() => {// 不论成功或失败,都会执行的操作console.log('Promise finished');});
-
.then(onFulfilled[, onRejected])
: 当 Promise 状态变为 Fulfilled 时,调用onFulfilled
函数,传入成功结果。可选地,当 Promise 状态变为 Rejected 时,调用onRejected
函数,传入错误原因。.then()
方法返回一个新的 Promise,其结果取决于回调函数的返回值或抛出的错误。 -
.catch(onRejected)
: 当 Promise 状态变为 Rejected 时,调用onRejected
函数,传入错误原因。.catch()
是.then(null, onRejected)
的简写,用于集中处理 Promise 链中的错误。返回一个新的 Promise。 -
.finally(onFinally)
: 不论 Promise 状态变为 Fulfilled 还是 Rejected,都会调用onFinally
函数。.finally()
方法返回一个新的 Promise,其状态和结果与原 Promise 相同。
Promise 链
Promise 支持链式调用 .then()
、.catch()
和 .finally()
方法,形成 Promise 链。每个方法返回一个新的 Promise,使得异步操作可以按顺序依次执行:
doSomethingAsync().then(result1 => {return doAnotherThingAsync(result1);}).then(result2 => {return yetAnotherAsync(result2);}).catch(error => {console.error('An error occurred:', error);}).finally(() => {console.log('All operations finished');});
在这个示例中,三个异步操作按顺序执行,每个操作的结果作为下一个操作的输入。如果任何操作失败,.catch()
会捕获并处理错误,.finally()
会在所有操作(无论成功或失败)完成后执行清理或通知操作。
Promise.all()、Promise.race() 和 Promise.any()
Promise 还提供了用于处理多个 Promise 的静态方法:
-
Promise.all(promises)
: 当传入的 Promise 数组promises
中的所有 Promise 都变为 Fulfilled 时,返回的 Promise 解析为包含所有结果的数组。如果有任何一个 Promise 变为 Rejected,返回的 Promise 立即变为 Rejected,传递第一个拒绝的原因。 -
Promise.race(promises)
: 当传入的 Promise 数组promises
中的第一个 Promise 变为 Fulfilled 或 Rejected 时,返回的 Promise 就以相同的解决值或拒绝原因结束。 -
Promise.any(promises)
: 当传入的 Promise 数组promises
中的第一个 Promise 变为 Fulfilled 时,返回的 Promise 就以该解决值结束。如果所有 Promise 都变为 Rejected,返回的 Promise 以 AggregateError(包含所有拒绝原因的聚合错误)拒绝。
总之,Promise 通过构造函数创建,通过 .then()
、.catch()
、.finally()
等方法使用,支持链式调用和多种组合模式,为异步编程提供了统一、优雅的解决方案。