MST

星途 面试题库

面试题:JavaScript 生成器在复杂异步任务并发与限流中的应用

设想有一个场景,需要同时发起大量的异步网络请求(比如 100 个),但由于网络资源和服务器负载限制,要求同一时间最多只能有 5 个请求处于活跃状态(并发限流)。请使用 JavaScript 生成器以及相关异步控制技术(如 Promise)来实现这个功能。实现过程中需要考虑请求失败后的重试机制,确保所有请求最终都能成功完成(除非达到最大重试次数)。详细阐述你实现的思路以及生成器在这个复杂异步任务管理中起到的关键作用。
33.5万 热度难度
编程语言JavaScript

知识考点

AI 面试

面试题答案

一键面试

实现思路

  1. 生成器函数:创建一个生成器函数,用于生成所有的异步请求任务。每次生成一个任务,通过 yield 暂停生成器,等待任务完成后再继续生成下一个任务。
  2. 任务队列:使用一个数组来存储所有待执行的任务,这些任务由生成器生成。
  3. 并发控制:利用一个计数器来记录当前处于活跃状态的请求数量,确保同一时间最多只有 5 个请求在执行。
  4. 重试机制:为每个请求设置最大重试次数,当请求失败时,在重试次数未达到上限的情况下,重新发起请求。

代码实现

function asyncRequest(url, maxRetries = 3) {
    return new Promise((resolve, reject) => {
        let retries = 0;
        function sendRequest() {
            // 模拟异步网络请求
            setTimeout(() => {
                const success = Math.random() > 0.5; // 模拟50%的失败率
                if (success) {
                    resolve(`Successfully fetched from ${url}`);
                } else if (retries < maxRetries) {
                    retries++;
                    sendRequest();
                } else {
                    reject(new Error(`Failed to fetch from ${url} after ${maxRetries} retries`));
                }
            }, 1000);
        }
        sendRequest();
    });
}

function* requestGenerator(urls) {
    for (const url of urls) {
        yield asyncRequest(url);
    }
}

async function executeRequests(urls, maxConcurrent = 5) {
    const tasks = [];
    const gen = requestGenerator(urls);
    let activeCount = 0;

    function nextTask() {
        const task = gen.next();
        if (!task.done) {
            tasks.push(task.value);
            activeCount++;
            task.value
              .then(() => {
                    activeCount--;
                    if (tasks.length > 0) {
                        nextTask();
                    }
                })
              .catch(error => {
                    console.error(error);
                    activeCount--;
                    if (tasks.length > 0) {
                        nextTask();
                    }
                });
        }
    }

    while (activeCount < maxConcurrent && tasks.length < urls.length) {
        nextTask();
    }

    await Promise.all(tasks);
}

// 示例使用
const urls = Array.from({ length: 100 }, (_, i) => `http://example.com/api/${i}`);
executeRequests(urls, 5);

生成器的关键作用

  1. 任务生成与暂停:生成器函数通过 yield 关键字可以暂停执行并返回一个值(即异步请求任务)。这使得我们可以按需逐个生成异步任务,而不是一次性生成所有任务,从而实现对任务的精细控制。
  2. 顺序执行与调度:生成器的 next() 方法可以恢复生成器的执行,通过这种方式,我们可以按照一定的顺序调度任务执行,结合并发控制逻辑,确保同一时间只有指定数量的任务处于活跃状态。
  3. 分离任务生成与执行:将任务的生成逻辑(在生成器函数中)与执行逻辑(在 executeRequests 函数中)分离,使得代码结构更加清晰,易于理解和维护。这种分离有助于在复杂的异步任务管理场景中,更好地组织和控制任务流。