首页/文章/javascript

js核心之async和await的本质

2024-07-10
22208 分钟
...

先有问题再有答案

  1. 如何理解async/await?
  2. async/await有哪些使用场景?
  3. 对于不支持async/await的低版本babel如何实现的?
  4. async/await与Generator是什么关系?
  5. async/await与promise是什么关系
  6. async/await与Iterator是什么关系
  7. async/await的本质是什么?

前置文章

  1. js核心之Generator函数
  2. js核心之Iterator迭代器
  3. js三座大山之异步三promise本质

设计目的

async/await 的设计主要是为了简化JavaScript的异步编程和提高代码的可读性和可维护性。

在JavaScript中处理异步操作,早期主要依赖于回调函数(callbacks)和事件监听,但这导致代码很容易陷入“回调地狱”,即回调函数套回调函数,使得代码难以阅读和理解。

后来Promise应运而生,虽然它改善了异步操作的书写方式,避免了多层的回调嵌套,但在某些复杂的异步逻辑处理中依然显得累赘。

而async/await是基于Promise实现的,它通过语言层面提供了更为优雅的异步方案。在语法上,async/await使得异步代码的书写和普通的同步代码几乎没有差别,大大提高了代码可读性,也便于开发者理解代码的执行逻辑。

同时,使用async/await也使得对异步操作的错误处理变得更为方便,可以直接使用try/catch结构进行异常处理,这与同步代码中的错误处理方式一致,从而提高代码的一致性和可维护性。

关于异步方案的发展背景可以参考这篇文章 js三座大山之异步二异步方案

babel是如何编译async/await

兼容性

编译后代码

这里需要先掌握好前置知识再来阅读编译后的代码

async function t(){
  const res1 = await 1;
  const res2 = await 2;
  const res3 = await 3;
  return res1 + res2 + res3
}

t().then(console.log); 

将babel目标设置为Safari 10 代码如下:

这里对参数名做了些改动便于理解。

        
        function asyncGeneratorStep(iterator, resolve, reject, _next, _throw, next, v) {
            try {
                
                var i = iterator[next](v);
                var u = i.value;
            } catch (n) {
                
                return void reject(n);
            }
            
            i.done ? resolve(u) : Promise.resolve(u).then(_next, _throw);
        }

        
        function _asyncToGenerator(generator) {
            return function () {
                var self = this;
                var arg = arguments;

                return new Promise(function (resolve, reject) {
                    var iterator = generator.apply(self, arg);
                    function _next(v) {
                        
                        asyncGeneratorStep(iterator, resolve, reject, _next, _throw, 'next', v);
                    }
                    function _throw(v) {
                        
                        asyncGeneratorStep(iterator, resolve, reject, _next, _throw, 'throw', v);
                    }
                    
                    _next(void 0);
                });
            };
        }
        
        function _async() {
            var _a = _asyncToGenerator(function* () {
                var res1 = yield 1;
                var res2 = yield 2;
                var res3 = yield 3;
                
                return res1 + res2 + res3;
            });
            
            return _a.apply(this, arguments);
        }

        
        function asyncBabelPolyfill() {
            return _async.apply(this, arguments);
        }

        
        asyncBabelPolyfill().then(console.log); 

分析:

调试

  1. asyncGeneratorStep 函数的作用: asyncGeneratorStep 是一个辅助函数,用于处理生成器函数的每一步执行。它接收以下参数:

    • iterator: 生成器函数的迭代器。
    • resolve: 当生成器完成时,用于解决 Promise 的回调函数。
    • reject: 当生成器抛出错误时,用于拒绝 Promise 的回调函数。
    • _next 和 _throw: 分别是生成器的 next 和 throw 方法的包装函数。
    • next: 表示当前操作是 next
    • v: 传递给生成器 next 方法的值, 也是yield的返回结果。

    当生成器函数执行到 yield 表达式时,它会暂停执行并返回一个对象,该对象包含 value 属性(yield 表达式的结果)和 done 属性(表示生成器是否完成)。asyncGeneratorStep 会根据 done 属性的值来决定是继续执行生成器的下一条 yield 表达式,还是解决 Promise。

  2. _asyncToGenerator 函数的作用: _asyncToGenerator 是一个高阶函数,它接收一个生成器函数 n 并返回一个新的函数。这个新函数在内部使用 asyncGeneratorStep 来处理生成器的每一步。当调用这个新函数时,它会创建一个 Promise,并在生成器函数中使用 _next 函数来启动生成器。每当生成器函数遇到 yield 表达式时,_next 函数会被调用,并将 yield 的结果作为参数传递给 asyncGeneratorStep。如果生成器函数抛出错误,_throw 函数会被调用来处理错误。

  3. _async 函数的作用: _async 函数是一个使用 _asyncToGenerator 转换的生成器函数。它定义了一个生成器,该生成器在每个 yield 表达式处暂停,并接受一个值,这些值最终被加和并返回。这个函数通过 _asyncToGenerator 转换后,返回了一个promise。

  4. asyncBabelPolyfill: 编译后的入口函数。

总结

babel将async/await转换为Generator函数,并且会自动遍历返回的迭代器,从yield表达式读取输入 并将结果通过next返回给调用方,然后封装为一个promise对象,当迭代器执行完毕,resolve这个promise对象,如果迭代过程中报错则reject这个promise。

所以async/await的本质我们可以理解为:async/await = Generator + 自动遍历Iterator + Promise

关于 void 0

使用 void 0 时,实际上是在调用 void 运算符并传递 0 作为参数。由于 void 运算符总是返回 undefined,所以 void 0 的结果也是 undefined

使用 void 0 而不是直接写 undefined 的一个原因是,undefined可以被重新赋值为其它值,而void 0一直保持是undefined。这样可以防止在那些可以改变undefined值的环境中出错。

undefined = "new value"; 
console.log(undefined); 
console.log(void 0); 

上面的代码中,undefined被重新赋值后,全局的undefined就不再真正是undefined了。而void 0在任何情况下都保持是undefined,这样就避免了由于不小心改变了undefined而导致的错误。所以在某些代码中,为了写出更加安全的代码,会选择使用void 0来代替undefined。

现在的JavaScript版本已经不允许更改全局的undefined值,它现在是只读(read-only)的。对于现代版本的JavaScript(ECMAScript 5 及以后),试图给undefined赋值会被忽略��在严格模式下,试图给undefined赋值会抛出错误)。

在生成器函数或异步函数的上下文中,使用 void 0 作为参数传递给 next 方法,通常是因为你想要开始执行生成器,但不需要从 yield 表达式中接收任何值。这与传递 undefined 是等效的.

async/await的缺陷

async/await 语法在 JavaScript 中提供了一种非常直观和简洁的方式来处理异步操作,它让异步代码看起来和同步代码非常相似,从而提高了代码的可读性和可维护性。然而,async/await 有一个缺点,即所谓的“异步传染性”。

这里的“异步传染性”指的是当你在函数内部使用 await 表达式时,你必须确保这个函数本身被声明为 async。这是因为 await 只能在 async 函数内部使用。这个要求确保了 await 表达式暂停执行的能力被正确地管理,并且任何在 await 表达式中抛出的错误都能被正确地捕获和处理。

想要解决“异步传染性”问题可以参考 这篇文章: js三座大山之异步四-Promise的同步调用消除异步的传染性

参考

  1. es6.ruanyifeng.com/#docs/async
  2. developer.mozilla.org/zh-CN/docs/…
  3. developer.mozilla.org/zh-CN/docs/…

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
javascript2025-09-18
JavaScript 的多线程能力:Worker
如果你写过一些计算量稍大的 JavaScript 代码,比如图像处理、大量数据排序或者复杂的算法,你几乎肯定遇到过浏览器“卡死”的现象。点击页面没反应,动画也停了,就像整个世界都静止了。 这就是主线程被阻塞的典型后果。因为主线程既要负责执行 JavaScript,又要负责渲染页面、响应用户操作,一旦它被繁重的计算任务占满,就无暇顾及其他,用户体验便直线下降。 这个问题的根源,正是“主线程是单线程的”。那么,如何解决呢? 答案很简单:把这...
学习面试
javascript2025-09-10
手把手教你实现一个EventEmitter
你是不是也遇到过这种情况:代码里各种事件监听和触发,回调函数套了一层又一层,最后自己都理不清哪个事件先触发、哪个后触发了? 别担心,今天我就带你从零开始实现一个自己的事件触发器(EventEmitter),让你彻底掌握事件驱动的精髓,代码清晰度直接提升200%! 为什么要懂EventEmitter? 先说说为什么我们要关心这个。Node.js的核心就是事件驱动,比如文件读写、网络请求,都是通过事件来处理的。不会EventEmitter,...
学习面试
javascript2025-08-09
Web Worker 多线程魔法
你是否曾遇到过网页因为复杂计算而卡顿的情况?今天,让我们一起探索Web Worker的神奇世界,看看如何用多线程技术解决性能瓶颈,并实现流畅的图片压缩功能! 为什么需要Web Worker? 想象一下,你在一个繁忙的餐厅里,只有一位服务员(主线程)负责点餐、上菜、结账所有工作。当客人突然暴增时,这位服务员就会手忙脚乱,其他客人只能干等着… 这就是JavaScript的现状—— 单线程 !所有任务都在一个线程中排队执行,一旦遇到复杂计算(...
学习面试
javascript2025-08-07
🌐AST抽象语法树
AST 是前端开发中一个非常重要的概念,它就像是代码的“编译器”,能够帮助我们理解和操作代码。本文将深入探讨 AST 的原理和应用,带你从零开始了解 AST。 一、什么是 AST? AST(抽象语法树)是一种树形结构,用于表示程序代码的语法结构。它将代码分解为一系列的节点,每个节点代表代码中的一个元素,如变量、函数、表达式等。AST 是编译器和解释器的基础,也是代码分析和转换的关键工具。 1\. AST 的结构 AST 的结构通常包括以...
学习面试
javascript2025-08-05
打字机效果是如何实现的
分析效果 使用 AI 对话的时候,文字是一个字一个字蹦出来的?它不是等所有内容生成完毕后一次性展示,而是以一种流畅、实时的“打字机”效果呈现,这种体验的背后,隐藏着一种高效的Web技术。它就是 服务器推送事件(Server Sent Events,简称 SSE) 什么是 SSE 从根本上说,SSE 是一种允许服务器 单向 、 持续 地向客户端(浏览器)推送数据的Web技术。 可以把它想象成一个 电台广播 : 服务器 是广播电台,持续不断...
学习面试
javascript2025-08-04
Promise 从原理到实现
前言 是 异步编程的核心概念之一,它为我们提供了一种更优雅的方式来处理异步操作。 本文将全面剖析 的工作原理,并逐步实现一个符合 规范的 。 正文 一、Promise 基础概念 代表一个异步操作的最终完成(或失败)及其结果值。它有 种状态: 1. pending : ,既不是成功,也不是失败状态 2. fulfilled :意味着操作 完成 3. rejected :意味着操作 状态一旦改变就不能再变,只能从 变为 ,或从 变为 。 二...
学习面试

评论

请登录后发表评论

去登录
加载评论中...

目录