生成器(Generator) 与 async / await

在了解生成器与async和await关系之前,需要先清楚一个概念即同步和异步,javascript在执行期间会产生许多任务,这些任务大致可以分为两类,分别是同步任务和异步任务。

首先什么是同步任务,同步任务的执行会阻塞并占用主线程,而浏览器渲染界面、解析DOM、生成渲染树则都在主线程完成,当没有开启多线程的情况下,同步任务过多且同步任务执行时间过长,将会长时间阻塞主线程,导致界面无法得到响应,甚至出现卡顿,用户体验不佳等问题,所以在编写同步任务时,不能在同步任务中出现耗时的操作,同步任务特点:立即执行,阻塞主线程渲染。

而异步任务则与同步任务相反,当执行到异步任务时,该异步任务不会立即执行,而是将该任务放入到对应的任务队列中等待执行,当所有同步任务执行完毕且主线程执行栈为空的情况下,则浏览器通过事件循环机制执行任务队列中的异步任务,异步任务主要包括 定时器、ajax、fetch、Promise、event等等,异步任务特点:延时执行,不阻塞主线程渲染。

由于异步任务是延时执行的,并且异步任务不是按照编写任务的顺序执行,我们不清楚异步任务会在什么时机下执行,所以异步任务通常变得不可控。

当我们有一种需求,需要在上一个异步任务执行完毕后,执行下一个指定的异步任务,这个时候就可以使用生成器(Generator)来解决该问题。

在javaScript中,生成器(Generator)是一种特殊的函数,它可以在函数执行期间产生多个值。通常,函数在执行时会一直运行直到返回一个值,然后停止。但是,生成器函数可以在需要时产生一个值,然后暂停执行,稍后可以恢复执行并生成下一个值, 这种功能非常有用,特别是在处理大量数据或者需要异步操作的情况下。

生成器函数使用function*语法来定义,内部包含一个或多个yield关键字,当调用生成器函数时,它会返回一个称为生成器对象的特殊类型的迭代器**, 通过调用生成器对象的next()方法,可以控制生成器函数的执行,并且每次调用都会返回一个包含当前生成值的对象,该对象具有value和done属性,value表示生成的值,done表示生成器函数是否已经完成。

生成器函数的主要优点之一是它们允许您以一种更易于理解和控制的方式编写异步代码,而不需要使用回调或者Promise。使用生成器和yield语句,可以编写像同步代码一样的异步代码,提高了代码的可读性和可维护性。

下面是生成器语法使用示例:

lua 复制代码
function* generator () {
  yield 1;
  yield 2;
  yield 3;
}
  
const actuators = generator();
actuators.next() // {value: 1, done: false}
actuators.next() // {value: 2, done: false}
actuators.next() // {value: 3, done: false}
actuators.next() // {value: undefined, done: true}

正因为生成器函数可以控制执行步骤,通过这一点就可以让异步任务按照顺序执行,让异步任务变得更加可控,当需要执行异步任务调用next即可,可以编写一个执行器来自动调用下一个next,以下是一个简单示例:

javascript 复制代码
function* asyncTaskGenerator() {
    const result1 = yield runAsyncTask1();
    const result2 = yield runAsyncTask2();
    const result3 = yield runAsyncTask3();
}

// 模拟异步任务
function runAsyncTask1() { 
    return new Promise(resolve => {
        setTimeout(() => {
            resolve("1");
        }, 1000);
    });
}

function runAsyncTask2() { 
    return new Promise(resolve => {
        setTimeout(() => {
            resolve("2");
        }, 1000);
    });
}

function runAsyncTask3() { 
    return new Promise(resolve => {
        setTimeout(() => {
            resolve("3");
        }, 1000);
    });
}

// 执行生成器函数,递归调用后续的异步任务
function runAsyncTasks() {   
    const asyncTaskIterator = asyncTaskGenerator();
    
    
// 第二次调用next方法,需要传入上一次的结果,在生成器变量中可获取到    
function handleNextTask(taskResult) {
    const nextTask = asyncTaskIterator.next(taskResult);
    
    if (!nextTask.done) {  
    // 将上一次异步任务的执行结果传递到下一个执行函数中    
        nextTask.value.then(handleNextTask);
    } 
}

    handleNextTask();
}

// 调用函数开始执行异步任务
runAsyncTasks();
// 最终输出的结果分别是间隔一秒输出 1 紧接着间隔两秒输出 2 然后间隔1.5秒输出 3

通过以上示例就可以实现异步任务按照顺序依次执行,但是上面示例方式繁琐而复杂,需要自定义执行器,而本文标题中出现的async/await就是以上示例的语法糖**,无需自定义执行器,可以将生成器函数中的 * 理解为async,而yield可以理解为await,下面通过async和await实现上面同样的效果

javascript 复制代码
// 模拟异步任务
function runAsyncTask1() { 
    return new Promise(resolve => {
        setTimeout(() => {
            resolve("1");
        }, 1000);
    });
}

function runAsyncTask2() { 
    return new Promise(resolve => {
        setTimeout(() => {
            resolve("2");
        }, 1000);
    });
}

function runAsyncTask3() { 
    return new Promise(resolve => {
        setTimeout(() => {
            resolve("3");
        }, 1000);
    });
}

async function asyncTaskGenerator() {
    const result1 = await runAsyncTask1();
    const result2 = await runAsyncTask2();
    const result3 = await runAsyncTask3();
}
asyncTaskGenerator();
相关推荐
用户4582031531715 分钟前
CSS特异性:如何精准控制样式而不失控?
前端·css
libraG35 分钟前
Jenkins打包问题
前端·npm·jenkins
前端康师傅35 分钟前
JavaScript 作用域
前端·javascript
前端缘梦35 分钟前
Vue Keep-Alive 组件详解:优化性能与保留组件状态的终极指南
前端·vue.js·面试
我是天龙_绍44 分钟前
使用 TypeScript (TS) 结合 JSDoc
前端
云枫晖1 小时前
JS核心知识-事件循环
前端·javascript
Simon_He1 小时前
这次来点狠的:用 Vue 3 把 AI 的“碎片 Markdown”渲染得又快又稳(Monaco 实时更新 + Mermaid 渐进绘图)
前端·vue.js·markdown
eason_fan2 小时前
Git 大小写敏感性问题:一次组件重命名引发的CI构建失败
前端·javascript
无羡仙2 小时前
JavaScript 迭代器
前端
XiaoSong2 小时前
从未有过如此丝滑的React Native开发体验:EAS开发构建完全指南
前端·react.js