MST

星途 面试题库

面试题:JavaScript生成器与迭代器在异步操作场景的优化

有一组异步任务,每个任务都返回一个Promise。这些任务需要按顺序执行,并且每个任务依赖前一个任务的结果。请使用生成器与迭代器来优化这种异步操作流程,写出代码实现并阐述为什么这种方式比传统链式调用Promise更具优势。
19.5万 热度难度
编程语言JavaScript

知识考点

AI 面试

面试题答案

一键面试

代码实现

function asyncTask1() {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve('result1');
        }, 1000);
    });
}

function asyncTask2(result1) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(result1 + ' -> result2');
        }, 1000);
    });
}

function asyncTask3(result2) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(result2 + ' -> result3');
        }, 1000);
    });
}

function* taskGenerator() {
    const result1 = yield asyncTask1();
    const result2 = yield asyncTask2(result1);
    const result3 = yield asyncTask3(result2);
    return result3;
}

function runGenerator(generator) {
    const iterator = generator();
    function step(result) {
        let { value, done } = iterator.next(result);
        if (done) {
            return value;
        }
        return Promise.resolve(value).then(step);
    }
    return step();
}

runGenerator(taskGenerator).then((finalResult) => {
    console.log(finalResult);
});

优势阐述

  1. 代码可读性
    • 在传统链式调用Promise中,随着异步任务增多,代码会形成“回调地狱”式的链式结构,使得代码难以阅读和维护。例如:
    asyncTask1()
       .then(result1 => asyncTask2(result1))
       .then(result2 => asyncTask3(result2))
       .then(finalResult => console.log(finalResult));
    
    • 而使用生成器与迭代器,异步任务以更接近同步代码的方式书写,每个异步操作在生成器函数内清晰地按顺序排列,如上述taskGenerator函数,更易于理解和修改。
  2. 错误处理
    • 传统链式调用Promise时,若其中一个Promise被拒绝,错误处理逻辑在链式的.catch中,定位具体出错的任务可能较复杂,尤其是多层嵌套时。
    • 生成器函数可以使用try...catch块来捕获错误,使得错误处理更直观和精准,更符合同步代码的错误处理习惯。例如:
    function* taskGenerator() {
        try {
            const result1 = yield asyncTask1();
            const result2 = yield asyncTask2(result1);
            const result3 = yield asyncTask3(result2);
            return result3;
        } catch (error) {
            console.error('Error occurred:', error);
        }
    }
    
  3. 代码复用
    • 生成器函数的每个部分可以单独提取和复用,生成器函数本身也可以作为一个独立的单元进行复用。例如,asyncTask1asyncTask2asyncTask3函数可以在其他生成器函数或异步操作中复用,而传统链式调用相对更耦合,复用性较差。