• 为什么 async/await 不仅仅是语法糖


    微信搜索 【大迁世界】, 我会第一时间和你分享前端行业趋势,学习途径等等。
    本文 GitHub https://github.com/qq449245884/xiaozhi 已收录,有一线大厂面试完整考点、资料以及我的系列文章。

    开篇观点,async/await 不仅仅是 Promise 上面的语法糖,因为 async/await 确实提供了切实的好处。

    • async/await 让异步代码变成同步的方式,从而使代码更具表现力和可读性。
    • async/await 统一了异步编程的经验;以及提供了更好的错误堆栈跟踪。

    关于 JS 中异步编程的一点历史

    异步编程在 JavaScript 中很常见。每当我们需要进行网络服务调用、文件访问或数据库操作时,尽管语言是单线程的,但异步性是我们防止用户界面被阻塞的方法。

    在 ES6 之前,回调是猿们处理异步编程的方式。我们表达时间依赖性(即异步操作的执行顺序)的唯一方法是将一个回调嵌套在另一个回调中,这导致了所谓的回调地狱

    Es6 中引入了 Promise,它是一个用于异步操作的一流对象,我们可以轻松地传递、组合、聚合和应用转换。时间上的依赖性通过 then方法链干净地表达出来。

    有了 Promise 这个强大的伙伴,听起来异步编程在 JS 中是一个已经解决的问题,对吗?

    恩,还没有,因为有时候 Promise 的级别太低了,不太适合使用。

    有时 Promise 的级别太低,不适合使用

    尽管出现了 Promise,但在 JS 中仍然需要一个更高级别的语言结构来进行异步编程。

    我们来看个例子, 假设我们需要某个函数在某个时间间隔轮询一个API。当达到最大重试次数时,它就会解析为 null

    下面是 Promise 的一种解决方案:

    let count = 0;
    
    function apiCall() {
      return new Promise((resolve) =>
        // a在第6次重试时,它被解析为 "value"。
        count++ === 5 ? resolve('value') : resolve(null)
      );
    }
    
    function sleep(interval) {
      return new Promise((resolve) => setTimeout(resolve, interval));
    }
    
    function poll(retry, interval) {
      return new Promise((resolve) => {
        // 为了简洁起见,跳过错误处理
    
        if (retry === 0) resolve(null);
        apiCall().then((val) => {
          if (val !== null) resolve(val);
          else {
            sleep(interval).then(() => {
              resolve(poll(retry - 1, interval));
            });
          }
        });
      });
    }
    
    poll(6, 1000).then(console.log); // 'value'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    这种解决方案的直观性和可读性取决于人们对Promise的熟悉程度,以及 Promise.resolve 如何 “平铺” Promise 和递归。对我来说,这不是写这样一个函数的最可读的方式。

    使用 async/await

    我们用 async/await 语法重写上述解决方案:

    async function poll(retry, interval) {
      while (retry >= 0) {
        const value = await apiCall().catch((e) => {}); 
        if (value !== null) return value;
        await sleep(interval);
        retry--;
      }
    
      return null;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    我想大多数人都会觉得上面的解决方案更有可读性,因为我们能够使用所有正常的语言结构,如循环、异步操作的 try-catch 等。

    这可能是 async/await 的最大卖点–使我们能够以同步的方式编写异步代码。另一方面,这可能是对 async/await 最常见的反对意见的来源,稍后再谈这个问题。

    顺便说一下,await甚至有正确的操作符优先级,所以await a + await b 等于(await a) + (await b),而不是让我们说await (a + await b)

    async/await 在同步和异步代码中提供了统一的体验

    async/await的另一个好处是,await自动将任何非Promise(non-thenables)包装成 Promises 。await的语义等同于Promise.resolve,这意味着可以 await 任何东西:

    function fetchValue() {
      return 1;
    }
    
    async function fn() {
      const val = await fetchValue();
      console.log(val); // 1
    }
    
    // 上面等同于下面
    
    function fn() {
      Promise.resolve(fetchValue()).then((val) => {
        console.log(val); // 1
      });
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    如果我们将 then 方法附加到从 fetchValue 返回的数字 1 上,就会出现以下错误。

    function fetchValue() {
      return 1;
    }
    
    function fn() {
      fetchValue().then((val) => {
        console.log(val);
      });
    }
    
    fn(); // ❌ Uncaught TypeError: fetchValue(...).then is not a function
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    最后, 从 async 函数返回的任何东西都是一个 Promise:

    Object.prototype.toString.call((async function () {})()); // '[object Promise]'
    
    • 1

    async/await 提供更好的错误堆栈跟踪

    V8工程师Mathias写了一篇名为Asynchronous stack traces: why await beats Promise#then() 的文章,介绍了为什么与 Promise相比,引擎更容易捕捉和存储 async/await 的堆栈跟踪。事例如下:

    async function foo() {
      await bar();
      return 'value';
    }
    
    function bar() {
      throw new Error('BEEP BEEP');
    }
    
    foo().catch((error) => console.log(error.stack));
    
    // Error: BEEP BEEP
    //     at bar (:7:9)
    //     at foo (:2:9)
    //     at :10:1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    async 版本正确地捕获了错误堆栈跟踪。

    我们再来看看 Promise 版本。

    function foo() {
      return bar().then(() => 'value');
    }
    
    function bar() {
      return Promise.resolve().then(() => {
        throw new Error('BEEP BEEP');
      });
    }
    
    foo().catch((error) => console.log(error.stack));
    
    // Error: BEEP BEEP  at :7:11
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    堆栈跟踪丢失。从匿名的箭头函数切换到命名的函数声明有一点帮助,但帮助不大:

    function foo() {
      return bar().then(() => 'value');
    }
    
    function bar() {
      return Promise.resolve().then(function thisWillThrow() {
        throw new Error('BEEP BEEP');
      });
    }
    
    foo().catch((error) => console.log(error.stack));
    
    // Error: BEEP BEEP
    //    at thisWillThrow (:7:11)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    async/await 常见反对意见

    async/await 主要有两种常见的反对意见。

    首先,当独立的异步函数调用可以用Promise.all并发处理时,如果我们还大量使用async/await 可能会导致滥用,这样会造成开发者不去试图了解 Promise 的幕后是如何工作,而只是一味的使用 async/await

    第二种情况更为微妙。一些函数式编程爱好者认为 async/await 会招致命令式编程。从 FP 程序员的角度来看,能够使用循环和 try catch 并不是一件好事,因为这些语言结构意味着副作用,并鼓励使用不那么理想的错误处理。

    我对这种说法待保留意见。FP程序员理所当然地关心他们程序中的确定性。他们希望对自己的代码有绝对的信心。为了达到这个目的,需要一个复杂的类型系统,其中包括Result等类型。但我不认为async/await本身与FP不相容。

    无论如何,对于大多数人来说,包括我在内,FP仍然是一种后天的味道(尽管我确实认为FP超级酷,而且我正在慢慢学习它)。async/await提供的正常控制流语句和try catch错误处理,对于我们在 JavaScript 中协调复杂的异步操作是非常宝贵的。这正是为什么说 “async/await只是一种语法糖” 是一种轻描淡写的说法。


    作者:zhenghao 译者:前端小智 来源:zhenghao

    原文:https://www.zhenghao.io/posts/await-vs-promise

    交流

    有梦想,有干货,微信搜索 【大迁世界】 关注这个在凌晨还在刷碗的刷碗智。

    本文 GitHub https://github.com/qq449245884/xiaozhi 已收录,有一线大厂面试完整考点、资料以及我的系列文章。

  • 相关阅读:
    [C语言]栈与队列——喵喵队,冲冲冲
    2D物理引擎 Box2D for javascript Games 第六章 关节和马达
    pygame入门之环境配置
    3.55 OrCAD中怎么批量修改属性值字体的大小?OrCAD的Occurrence属性与Instance属性是什么含义?
    猿创征文 第二季| #「笔耕不辍」--生命不息,写作不止#
    虚基类设计 c++
    STC8单片机PWM定时器+EC11编码器实现计数
    OpenCV图像处理学习七,利用回调函数setMouseCallback和鼠标响应处理函数onMouse实现ROI感兴趣区的提取
    【毕业设计】深度学习试卷批改系统 - opencv python 机器视觉
    并发与多线程(5) 条件变量 condition_variable
  • 原文地址:https://blog.csdn.net/qq449245884/article/details/126635761