JavaScript中async与await实现原理与细节

目录
  • 一、回调地狱
  • 二、Promise
  • 三、生成器(generator)
  • 四、使用生成器同步化promise
  • 五、async、await异步代码究极解决方案

一、回调地狱

在es6兴起之后许多人都开始使用promise,promise目的是解决es5中的回调地狱(callback hell),那么什么是回调地狱呢?先来提一个需求,现在需要发送n个request请求,第二个请求参数需要第一个请求的结果,第三个请求的参数需要第二个请求的结果,以此类推... ,在没有promise的条件下,我们不难使用callback写出如下的代码:

function ajax(url, callback) {
    setTimeout(() => {
        callback(Math.random() + url)
    }, 1000);
}

function request() {
    ajax('url1', (res1 => {
        ajax(`url2?random=${res1}`, (res2) => {
            ajax(`url3?random=${res2}`, (res3) => {
                ajax(`url4?random=${res3}`, (res4) => {
                    // do something
                })
            })
        })
    }))
}
request()

二、Promise

这样确实能实现我们的需求,但是这样子的代码有什么缺点呢?不难看出我们的request函数越来越像个三角形 ,代码集中在上部分,下半部分全都是我们的括号,代码阅读性极差! 这时候我们的promise应运而生了,使用promise我们可以这样重构我们的代码如下:

function ajax(url) {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(Math.random() + url)
        }, 1000);
    })
}

function request() {
    ajax('url1').then(res1 => {
        ajax(`url2?random=${res1}`).then((res2) => {
            ajax(`url3?random=${res2}`).then((res3) => {
                ajax(`url4?random=${res3}`).then((res4) => {
                    // do something
                })
            })
        })
    })
}
request()

肯定有人说,这不还是像个三角形吗?这样使用promise有什么意义呢?此时我们可以借助promise的链式调用重构成下面这样:

function ajax(url) {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(Math.random() + url)
        }, 1000);
    })
}

function request() {
    ajax('url1').then(res1 => {
        return ajax(`url2?random=${res1}`)
    }).then(res2 => {
        return ajax(`url3?random=${res2}`)
    }).then(res3 => {
        return ajax(`url4?random=${res3}`)
    }).then(res4 => {
        // do something
    })
}

request()

相对于之前的回调地狱,此时我们的代码是不是比较清晰了。但是!这还不够!这看上去还不够直观。我们想要的是阅读异步代码,类似于阅读同步代码的方式一样方便。

三、生成器(generator)

生成器是es6新增的语法,它是一个特殊的迭代器,它可以用来暂停我们函数的执行!这个功能非常强大! 生成器的语法是,在声明函数时在后面增加一个 * 号,那么这个函数就是生成器函数,直接调用该函数得到的是一个生成器句柄,该生成器是不会执行的,必须要调用生成器句柄的next()方法后,生成器才会执行,并且执行到我们的yield处(如果存在yield就执行到第一个yield,不存在则直接执行完毕),该方法的返回值一个对象,结构是 {done: true/false, value: 我们yield后面跟的值} ,如果执行到该生成器函数末尾则 done为true。 关于生成器的知识可以点此JavaScript中的迭代器和可迭代对象与生成器

function* foo() {
    console.log('======');
    const a = yield 1;
    console.log('a',a);
}

const g = foo()
console.log('11111111')
const res1 = g.next()
console.log(res1)
const res2 = g.next('22222')
console.log(res2)

上面代码打印顺序为:

11111111
======
{done: false, value: 1}
'a','22222'
{done: true, value: undefined}

细心的你一定看出了,我们在next方法中传的参数会赋值给生成器函数中的yield 左侧,并可以在生成器中拿到这个值后进行使用。

四、使用生成器同步化promise

掌握了生成器的知识我们就可以使用生成器来将我们的promise链式调用进行重构如下:

function ajax(url) {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(Math.random() + url)
        }, 1000);
    })
}

function* request() {
    const res1 = yield ajax('url1')
    const res2 = yield ajax(`url2?random=${res1}`)
    const res3 = yield ajax(`url3?random=${res2}`)
    const res4 = yield ajax(`url4?random=${res3}`)
    //    do something
    console.log(res4);
}
// 开始调用我们的生成器
const generator = request();
generator.next().value.then(res1 => {
    generator.next(res1).value.then(res2 => {
        generator.next(res2).value.then(res3 => {
            generator.next(res3).value.then(res4 => {
                generator.next(res4)
            })
        })
    })
})

可以看到我们的生成器还是三角形,优化一下成链式调用如下:

generator.next().value.then(res1 => {
    return generator.next(res1).value
}).then(res2 => {
    return generator.next(res2).value
}).then(res3 => {
    return generator.next(res3).value
}).then(res4 => {
    generator.next(res4)
})

此时,我们的主函数已经非常像同步代码了,但是缺点是我们目前必须手动调用该生成器,并且request主函数里面我们不知道有多少次yield调用,因此我们的生成器也不能手动调用多次,这时,我们将该生成器调用代码进行重构,重构成可以自动执行我们的生成器的函数,不需要关心request内部有多少次yield使用,重构如下:

function ajax(url) {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(Math.random() + url)
        }, 1000);
    })
}
function* request() {
    const res1 = yield ajax('url1')
    const res2 = yield ajax(`url2?random=${res1}`)
    const res3 = yield ajax(`url3?random=${res2}`)
    const res4 = yield ajax(`url4?random=${res3}`)
    //    do something
    console.log(res4);
}
function execGenerator(generatorFn) {
    const generator = generatorFn();
    function exec(res) {
        const { done, value } = generator.next(res)
        if (!done) {
            value.then(exec)
        }
    }
    exec()
}
execGenerator(request)

我们增加了一个自动执行函数execGenerator,该函数接受一个生成器参数,并且在内部自动进行递归调用,直至返回值的 done 属性为 true,此时我们的使用方式只需要定义一个request生成器函数,并且执行一下我们的自动执行函数 execGenerator ,我们的request就能像同步代码一样盘跑起来了,并且看起来非常直观。

五、async、await异步代码究极解决方案

其实async与await是我们上面生成器的语法糖而已,在内部做的事情跟我们使用生成器做的事情是一样的,使用方式如下:

function ajax(url) {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(Math.random() + url)
        }, 1000);
    })
}

async function request() {
    const res1 = await ajax('url1')
    const res2 = await ajax(`url2?random=${res1}`)
    const res3 = await ajax(`url3?random=${res2}`)
    const res4 = await ajax(`url4?random=${res3}`)
    //    do something
    console.log(res4);
}

看起来是不是跟我们的生成器request函数非常类似呢?使用asyncawait可以让我们省去写execGenerator函数的步骤,更加方便了我们的开发!

到此这篇关于JavaScript中async与await实现原理与细节的文章就介绍到这了,更多相关JS 中async与await 内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • js基于div丝滑实现贝塞尔曲线

    目录 引言 分析 实现 布局 如何计算y的偏移量 Math.sin() 和 Math.cos() 正弦曲线 余弦曲线 计算更多信息 完善剩余 完成跑马灯制作 贝塞尔曲线 简单封装一下方法 完整示例 style SCript 引言 今天遇到朋友发来的一个ui图,询问我如何实现下图这样的效果[vue项目],(听说是类似LED灯的展示效果),于是便有了今天的小demo,要实现一个类似下图的动效,上面的灯会一直重复滚动,但是这个并不是什么难点,主要在于如何实现这种平滑的曲线,日常我们的开发的div在我们

  • JavaScript Canvas实现噪点滤镜回忆童年电视雪花屏

    目录 引言 正文 初始化渲染 雪花屏动画 帧动画播放 引言 相信很多人80,90后的同学对童年里电视机的突然出现刺啦刺啦的雪花屏记忆犹新,本期将用 pixi.js 来完成一个电视机播放动漫然后突然出现雪花屏的动画,里面主要讲解了如何使用pixi.js播放帧动画和如何用噪点滤镜制造雪花屏. 正文 初始化渲染 import * as PIXI from "pixi.js" const GAME_WIDTH = 800; const GAME_HEIGHT = 600; export def

  • JS实现羊了个羊小游戏实例

    目录 引言 rem布局方案 popbox.js使用原理 html代码 样式代码 javascript代码 导入图片素材列表 startHandler函数实现 randomList 工具方法 clickHandler函数内部 createShadow方法 startHandler后续的逻辑 引言 这两天火爆全场的<羊了个羊>游戏,相信大家都玩过了,那么在玩这个游戏的同时,我想大家都会好奇这个游戏的实现,本文就带大家使用css,html,js来实现一个动物版的游戏. 首先我用到了2个插件,第一个插

  • JavaScript中的canvas 实现一个圆环渐变倒计时效果

    目录 前言 1.效果图展示 2.需求分析 3.实现的技术 4.实现的过程 1. HTML 部分 2. SASS部分 3. JavaScript部分 5.全部源码 1.index.html 2. style.scss 3. index.js 前言 内容: 效果图 需求分析 实现技术 实现过程 全部源码 1.效果图展示 随着时间的减少, 圆环的红黄色部分会慢慢的减少,圆环中的数字会变小,一直到0停止. 2.需求分析 可以自定义倒计时结束的时间 圆环的颜色是渐变的 倒计时的动画在视觉上是流畅运行, 而

  • js 实现验证码输入框示例详解

    目录 前言 思路 遇到的问题 HTML CSS JS 前言 验证码输入框应该是很常见的需求吧,不知道各位小伙伴在在遇到的时候是选择找一找插件还是自己动手撸一个呢?我花了点时间撸了一个出来,实际体验还不错,分享给大家. 思路 我在实现之前肯定也是上网搜了一下的,网上的方式大多是使用多个input标签来实现,但我觉得不太优雅,就自己想了一个方法.使用了6个div标签和一个input标签.验证码长度一般是4位或6位,我这里用6位做演示. 先将6个div使用flex布局平铺. 再将input使用绝对定位

  • js 实现Material UI点击涟漪效果示例

    目录 正文 HTML CSS JS 实现效果 正文 我个人而言还是挺喜欢Material UI这套设计风格的.一些细节方面做的还不错.就比如今天要给大家分享的点击涟漪效果.Material UI里面叫做Ripples.好了,话不多说,开始吧. HTML <div class="example">Click me</div> CSS .example { position: relative; width: 300px; height: 300px; line-h

  • JavaScript中async与await实现原理与细节

    目录 一.回调地狱 二.Promise 三.生成器(generator) 四.使用生成器同步化promise 五.async.await异步代码究极解决方案 一.回调地狱 在es6兴起之后许多人都开始使用promise,promise目的是解决es5中的回调地狱(callback hell),那么什么是回调地狱呢?先来提一个需求,现在需要发送n个request请求,第二个请求参数需要第一个请求的结果,第三个请求的参数需要第二个请求的结果,以此类推... ,在没有promise的条件下,我们不难使

  • JavaScript中async和await的使用及队列详情

    目录 一.宏任务和微任务的队列入门知识,可以参考之前的文章: 1.async && await概念 2.async && await基本使用 二.async&& await结合promise在面试时回遇到的队列问题 三.总结 一.宏任务和微任务的队列入门知识,可以参考之前的文章: [JavaScript的事件循环机制] 宏任务和微任务在前端面试中,被经常提及到,包括口头和笔试题 1.async && await概念 async: 使用asyn

  • Javascript中async与await的捕捉错误详解

    目录 async与await捕捉错误 正常的输出时 trycatch捕捉错误 多个异步嵌套时 await-to-js 异步嵌套使用了try,代码相对不够智能 总结 async与await捕捉错误 正常的输出时 <template> <div class="hello"> </div> </template> <script> export default { name: 'HelloWorld', created() { th

  • JavaScript中async,await的使用和方法

    JS中 async函数和await 关键字 function hellworld() { return "您好!美好世界!"; } console.log(hellworld()); // 您好!美好世界! async function asyHellworld() { return "您好!美好世界!"; } console.log(asyHellworld()); // Promise { '您好!美好世界!' } 普通函数 hellworld 将简单地返回字符

  • JavaScript中async await更优雅的错误处理方式

    目录 背景 为什么要错误处理 async await 更优雅的错误处理 小结 总结 背景 团队来了新的小伙伴,发现我们的团队代码规范中,要给 async  await 添加 try...catch.他感觉很疑惑,假如有很多个(不集中),那不是要加很多个地方?那不是很不优雅? 为什么要错误处理 JavaScript 是一个单线程的语言,假如不加 try ...catch ,会导致直接报错无法继续执行.当然不意味着你代码中一定要用 try...catch 包住,使用 try...catch 意味着你

  • 浅谈C#中的Async和Await的用法详解

    众所周知C#提供Async和Await关键字来实现异步编程.在本文中,我们将共同探讨并介绍什么是Async 和 Await,以及如何在C#中使用Async 和 Await. 同样本文的内容也大多是翻译的,只不过加上了自己的理解进行了相关知识点的补充,如果你认为自己的英文水平还不错,大可直接跳转到文章末尾查看原文链接进行阅读. 写在前面 自从C# 5.0时代引入async和await关键字后,异步编程就变得流行起来.尤其在现在的.NET Core时代,如果你的代码中没有出现async或者await

  • 一篇文章弄懂C#中的async和await

    目录 前言 async await 从以往知识推导 创建异步任务 创建异步任务并返回Task 异步改同步 说说 await Task 说说 async Task<TResult> 同步异步? Task封装异步任务 关于跳到 await 变异步 为什么出现一层层的 await 总结 前言 本文介绍async/Task.在学习该知识点过程中,一定要按照每一个示例,去写代码.执行.输出结果,自己尝试分析思路. async 微软文档:使用 async 修饰符可将方法.lambda 表达式或匿名方法指定

  • C#5.0中的异步编程关键字async和await

    一.Asynchronous methods 异步方法 .NET 4.5 的推出,对于C#又有了新特性的增加——就是C#5.0中async和await两个关键字,这两个关键字简化了异步编程. 使用async修饰的方法被称为异步方法,这个方法调用时应该在前面加上await. 异步方法命名应该以Async结尾,这样大家知道调用的时候使用await. async和await关键字只是编译器的功能,编译器最终会用Task类创建代码. 1.创建返回任务的异步方法 建立一个同步方法Greeting,该方法在

  • 浅谈Async和Await如何简化异步编程(几个实例让你彻底明白)

    引言 C#5.0中async和await两个关键字,这两个关键字简化了异步编程,之所以简化了,还是因为编译器给我们做了更多的工作,下面就具体看看编译器到底在背后帮我们做了哪些复杂的工作的. 同步代码存在的问题 对于同步的代码,大家肯定都不陌生,因为我们平常写的代码大部分都是同步的,然而同步代码却存在一个很严重的问题,例如我们向一个Web服务器发出一个请求时,如果我们发出请求的代码是同步实现的话,这时候我们的应用程序就会处于等待状态,直到收回一个响应信息为止,然而在这个等待的状态,对于用户不能操作

  • JavaScript中instanceof与typeof运算符的用法及区别详细解析

    JavaScript中的instanceof和typeof常被用来判断一个变量是什么类型的(实例),但它们的使用还是有区别的: typeof 运算符返回一个用来表示表达式的数据类型的字符串. typeof expression ; expression 参数是需要查找类型信息的任意表达式. 说明typeof 是一个一元运算符,放在一个运算数之前. typeof 运算符把类型信息当作字符串返回.typeof 返回值有六种可能: "number" ,"string",

随机推荐