MST

星途 面试题库

面试题:JavaScript可迭代对象遍历中的异步处理及优化

假设你有一个包含大量异步操作(例如多个异步API调用)的可迭代对象,需要对其进行遍历处理。请描述如何在遍历过程中高效地管理这些异步操作,避免出现回调地狱,同时保证性能最优。可以使用ES6的async/await、Promise等相关技术,并分析不同实现方式的优缺点。
46.9万 热度难度
编程语言JavaScript

知识考点

AI 面试

面试题答案

一键面试

使用 async/await 结合 for...of 循环

  1. 代码示例
async function processIterable(iterable) {
    for (const item of iterable) {
        try {
            const result = await asyncOperation(item);
            console.log(result);
        } catch (error) {
            console.error(error);
        }
    }
}
function asyncOperation(item) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (Math.random() > 0.5) {
                resolve(`Processed ${item}`);
            } else {
                reject(new Error(`Error processing ${item}`));
            }
        }, 100);
    });
}
const iterable = [1, 2, 3, 4, 5];
processIterable(iterable);
  1. 优点
    • 代码结构清晰,类似于同步代码,可读性强,有效避免回调地狱。
    • 可以方便地使用 try...catch 进行错误处理,使得错误处理更直观。
  2. 缺点
    • 由于 await 会暂停函数执行,直到Promise被解决,所以异步操作是顺序执行的。如果异步操作之间没有依赖关系,这种方式可能会导致性能问题,因为没有充分利用并行处理的优势。

使用 Promise.all

  1. 代码示例
function processIterable(iterable) {
    const promises = iterable.map(async item => {
        try {
            return await asyncOperation(item);
        } catch (error) {
            return error;
        }
    });
    return Promise.all(promises);
}
function asyncOperation(item) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (Math.random() > 0.5) {
                resolve(`Processed ${item}`);
            } else {
                reject(new Error(`Error processing ${item}`));
            }
        }, 100);
    });
}
const iterable = [1, 2, 3, 4, 5];
processIterable(iterable).then(results => {
    console.log(results);
}).catch(error => {
    console.error(error);
});
  1. 优点
    • 所有异步操作并行执行,大大提高了整体的执行效率,适合异步操作之间没有依赖关系的场景。
    • 可以通过 Promise.all 返回的Promise统一处理所有操作的结果或错误。
  2. 缺点
    • 如果其中一个Promise被拒绝,Promise.all 会立即拒绝,即使其他Promise可能还在执行。这可能导致部分操作白执行,在某些场景下不是期望的行为。
    • 错误处理相对 async/awaittry...catch 不够直观,需要通过 .catch 来捕获整个 Promise.all 的错误。

使用 Promise.allSettled

  1. 代码示例
function processIterable(iterable) {
    const promises = iterable.map(async item => {
        try {
            return await asyncOperation(item);
        } catch (error) {
            return error;
        }
    });
    return Promise.allSettled(promises);
}
function asyncOperation(item) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (Math.random() > 0.5) {
                resolve(`Processed ${item}`);
            } else {
                reject(new Error(`Error processing ${item}`));
            }
        }, 100);
    });
}
const iterable = [1, 2, 3, 4, 5];
processIterable(iterable).then(results => {
    results.forEach(result => {
        if (result.status === 'fulfilled') {
            console.log(result.value);
        } else {
            console.error(result.reason);
        }
    });
});
  1. 优点
    • 所有异步操作并行执行,提高效率。
    • 无论每个Promise是成功还是失败,Promise.allSettled 都会等到所有Promise都完成,并返回一个包含每个Promise结果的数组,便于统一处理所有操作的结果,即使部分操作失败也不会影响其他操作的继续执行和结果收集。
  2. 缺点
    • 代码复杂度相对较高,需要遍历结果数组分别处理成功和失败的情况。
    • 相较于 Promise.all,如果所有操作都成功,Promise.allSettled 会多一些不必要的处理,因为它要等待所有操作完成并构建结果数组。