在前端开发中,异步操作是非常常见的。而在异步操作中,我们经常会遇到需要并行执行多个异步任务的情况。ES6 中引入了 Promise.all 方法来解决这个问题,但是在实际应用中,我们还需要注意一些细节,以确保异步操作的正确性和性能。本篇文章将介绍 ES8 中的 Async 函数,并分享一些正确处理 Promise.all 并行异步执行的技巧。
Async 函数简介
Async 函数是 ES8 中新增的异步操作语法,它使得异步操作的编写和处理更加简单和直观。Async 函数的定义方式如下:
async function foo() { // 异步操作 }
Async 函数返回一个 Promise 对象,可以使用 await 关键字来等待异步操作的结果。例如:
async function foo() { const result = await someAsyncOperation(); console.log(result); }
在上面的代码中,当 someAsyncOperation() 执行完成后,才会执行 console.log(result),因为 await 关键字会等待异步操作完成后再继续执行下面的代码。
Promise.all 并行异步执行的问题
在实际应用中,我们经常需要并行执行多个异步任务,并在所有任务都完成后进行一些操作。ES6 中的 Promise.all 方法可以很方便地实现这个功能。例如:
Promise.all([promise1, promise2, promise3]) .then(results => { console.log(results); }) .catch(error => { console.error(error); });
上面的代码会同时执行 promise1、promise2 和 promise3,等它们都完成后,才会执行 then 方法中的回调函数。回调函数的参数 results 是一个数组,包含了所有异步任务的结果。
然而,如果其中一个异步任务出现了错误,Promise.all 就会直接跳转到 catch 方法中,而不会等待其他任务完成。这可能会导致一些问题,例如:
- 如果某个任务的错误是致命的,我们可能需要立即停止其他任务的执行。
- 如果某个任务的错误是可恢复的,我们可能需要在其他任务完成后重新执行该任务。
因此,在使用 Promise.all 并行异步执行时,我们需要注意一些细节,以确保异步操作的正确性和性能。
正确处理并行异步执行的技巧
1. 使用 Promise.race 监听任务的完成情况
Promise.race 方法可以监听多个异步任务的完成情况,并返回最先完成的任务的结果或错误。例如:
Promise.race([promise1, promise2, promise3]) .then(result => { console.log(result); }) .catch(error => { console.error(error); });
上面的代码会同时执行 promise1、promise2 和 promise3,但只会返回最先完成的任务的结果或错误。我们可以利用这个特性来监听任务的完成情况,并在必要时停止其他任务的执行。
例如,以下代码演示了如何在其中一个任务出现错误时,立即停止其他任务的执行:
const promises = [promise1, promise2, promise3]; Promise.race(promises.map(p => p.catch(() => Promise.reject()))) .then(result => { console.log(result); }) .catch(error => { console.error(error); promises.forEach(p => p.abort && p.abort()); });
上面的代码中,我们首先使用 map 方法将所有任务包装成一个新的 Promise,该 Promise 在任务出现错误时会立即返回一个 rejected 状态的 Promise。然后,我们使用 Promise.race 监听所有任务的完成情况,并在其中一个任务出现错误时,立即停止其他任务的执行。
2. 使用 Promise.allSettled 处理可恢复错误
Promise.allSettled 方法可以等待所有异步任务完成后,返回一个包含所有任务的结果或错误的数组。该方法不会抛出错误,而是会将所有任务的结果包装成一个对象,并添加一个状态字段(fulfilled 或 rejected)。例如:
Promise.allSettled([promise1, promise2, promise3]) .then(results => { console.log(results); });
上面的代码会等待 promise1、promise2 和 promise3 执行完成后,返回一个包含所有任务结果的数组,其中每个任务结果都被包装成一个对象,并添加了一个状态字段。
我们可以利用 Promise.allSettled 方法来处理可恢复错误的情况。例如,以下代码演示了如何在所有任务完成后,重新执行出现错误的任务:
const promises = [promise1, promise2, promise3]; Promise.allSettled(promises) .then(results => { const errors = results.filter(result => result.status === 'rejected'); if (errors.length > 0) { return Promise.all(errors.map(error => error.reason.retry())); } }) .then(() => { console.log('All tasks completed successfully!'); }) .catch(error => { console.error(error); });
上面的代码中,我们首先使用 Promise.allSettled 方法等待所有任务执行完成后,返回一个包含所有任务结果的数组。然后,我们使用 filter 方法筛选出所有出现错误的任务,并使用 map 方法将错误对象转换成可恢复错误对象。最后,我们使用 Promise.all 方法重新执行所有出现错误的任务,并在所有任务完成后,输出一个成功的消息。
3. 使用 async 函数简化代码
使用 Async 函数可以使代码更加简洁和直观。例如,以下代码演示了如何使用 Async 函数来处理并行异步执行:
async function parallel(tasks) { const promises = tasks.map(task => task()); const results = await Promise.all(promises.map(p => p.catch(() => Promise.reject()))); const errors = results.filter(result => result instanceof Error); if (errors.length > 0) { await Promise.all(errors.map(error => error.retry())); return parallel(tasks); } return results; } parallel([task1, task2, task3]) .then(results => { console.log(results); }) .catch(error => { console.error(error); });
上面的代码中,我们定义了一个 Async 函数 parallel,它接受一个任务数组,每个任务都是一个返回 Promise 的函数。在函数内部,我们首先使用 map 方法将所有任务包装成一个 Promise 数组,并使用 Promise.all 方法等待所有任务执行完成后,返回一个包含所有任务结果的数组。然后,我们使用 filter 方法筛选出所有出现错误的任务,并使用 map 方法将错误对象转换成可恢复错误对象。最后,我们使用 await 关键字重新执行所有出现错误的任务,并在所有任务完成后,返回一个包含所有任务结果的数组。
示例代码
以下是一个完整的示例代码,演示了如何使用 Async 函数处理并行异步执行的问题:
class Task { constructor(name, fn, retryCount = 0) { this.name = name; this.fn = fn; this.retryCount = retryCount; } async run() { try { const result = await this.fn(); console.log(`${this.name} completed with result: ${result}`); return result; } catch (error) { console.error(`${this.name} failed with error: ${error}`); if (this.retryCount > 0) { console.log(`Retrying ${this.name} (${this.retryCount} left)...`); this.retryCount--; return this.run(); } else { throw error; } } } retry() { return new Task(this.name, this.fn, this.retryCount + 1); } } async function parallel(tasks) { const promises = tasks.map(task => task.run()); const results = await Promise.all(promises.map(p => p.catch(() => Promise.reject()))); const errors = results.filter(result => result instanceof Error); if (errors.length > 0) { await Promise.all(errors.map(error => error.retry().run())); return parallel(tasks); } return results; } const task1 = new Task('Task 1', async () => { await new Promise(resolve => setTimeout(resolve, 1000)); return 'Task 1 Result'; }); const task2 = new Task('Task 2', async () => { await new Promise(resolve => setTimeout(resolve, 500)); throw new Error('Task 2 Error'); }); const task3 = new Task('Task 3', async () => { await new Promise(resolve => setTimeout(resolve, 1500)); return 'Task 3 Result'; }); parallel([task1, task2, task3]) .then(results => { console.log(results); }) .catch(error => { console.error(error); });
上面的代码中,我们定义了一个 Task 类,它表示一个异步任务,包含了任务名称、任务执行函数和重试次数。在 Task 类中,我们定义了一个 run 方法,用来执行任务,并在出现错误时,自动重试任务。然后,我们定义了一个 parallel 函数,它接受一个 Task 数组,使用 Async 函数和 Promise.all 方法来执行所有任务,并在出现错误时,自动重试出现错误的任务。最后,我们定义了三个示例任务 task1、task2 和 task3,使用 parallel 函数来执行所有任务。
总结
本篇文章介绍了 ES8 中的 Async 函数,并分享了一些正确处理 Promise.all 并行异步执行的技巧。在实际应用中,我们需要注意一些细节,以确保异步操作的正确性和性能。使用 Async 函数可以使代码更加简洁和直观,但也需要注意一些细节,以确保代码的正确性和性能。
来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/658bbcaeeb4cecbf2d0f9be8