CHANSHIYU
GITHUBZERO
  • README
  • 時雨
    • 2017
      • 01 网站动态标题的两种方式
      • 02 RN App 外部唤醒踩坑记
    • 2018
      • 01 不一样の烟火
      • 02 Python 之禅
      • 03 Python 文件操作
    • 2019
      • 01 Aurora 食用指南
      • 02 Godaddy 域名找回记事
      • 03 一个接口的诞生
      • 04 SpringMVC 前后端传参协调
      • 05 主题集成友链访问统计
      • 06 Github Style 博客主题
      • 07 字符编码の小常识
      • 08 WSL 安装 Docker 实录
      • 09 Eriri comic reader
      • 10 Aurora 2.0
      • 11 jsDelivr 全站托管
      • 12 两年工作台变迁史
      • 13 春物
      • 14 一种优雅の笔记方式
    • 2020
      • 01 Telegram 电报机器人
      • 02 她的眼里有星辰
      • 03 文心雕龙
      • 04 软萌木子の有趣笔谈
      • 05 Telegram RSS 订阅频道
      • 06 水月雨银色飞船
      • 07 五年前旧照
    • 2021
      • 01 春宵苦短 2020
      • 02 风花
    • 2022
      • 01 小城新貌
      • 02 原神满级纪念
    • 2023
      • 01 2022 逆旅
      • 02 半透明背景图实现
      • 03 新年攒台海景房
  • 前端
    • JavaScript
      • 01 JavaScript 秘密花园
      • 02 JavaScript 正则技巧
      • 03 从浏览器解析 JS 运行机制
      • 04 Canvas 基础用法
      • 05 Blob Url And Data Url
      • 06 函数节流与函数防抖
      • 07 排序算法初探
      • 08 洗牌算法实现数组乱序
      • 09 正则匹配 match 和 exec
      • 10 正则匹配汉字
      • 11 JSX.Element vs ReactElement
      • 12 可选链与空值合并
      • 13 TypeScript 编码规范
      • 14 Typescript 中 interface 和 type 区别
      • 15 TypeScript 高级类型
      • 16 TypeScript 关键字
      • 17 TypeScript 映射类型
    • CSS
      • 01 Flex 弹性布局
      • 02 Position 定位
      • 03 CSS 逻辑属性
    • Node
      • 01 Node Tips
      • 02 七天学会 NodeJS
    • Note
      • 01 Note
      • 02 Code
      • 03 Snippets
      • 04 Git
    • React
      • 01 React Props Children 传值
      • 02 Use a Render Prop!
      • 03 React Hook
      • 04 React Hook 定时器
      • 05 Fetch data with React Hooks
      • 06 React 和 Vue 中 key 的作用
      • 07 useCallback 的正确使用方式
      • 08 useLayoutEffect 和 useEffect 的区别
      • 09 forwardRef 逃生舱
      • 10 React 条件渲染
    • Vue
      • 01 Vue Tips
      • 02 Vue 构建项目写入配置文件
      • 03 Vue 项目引入 SVG 图标
      • 04 Vue 一键导出 PDF
      • 05 动态可响应对象
      • 06 Vue 引入 SCSS
      • 07 Vue 路由权限控制
    • 实战系列
      • 01 WebSocket 心跳重连机制
      • 02 图片加解密二三事
      • 03 优雅实现 BackTop
      • 04 动态加载 JS 文件
      • 05 常用 DOM 方法比较
      • 06 AbortController 中断 fetch
      • 07 计算字符所占字节数
      • 08 Axios 自定义返回值类型
  • 后端
    • Java
      • 01 面向对象基本特征与原则
      • 02 Java 数据类型
      • 03 Java String
      • 04 Java 只有值传递
      • 05 Java final 与 static
      • 06 Java Object 通用方法
      • 07 Java 继承
      • 08 Java 反射
      • 09 Java 异常
      • 10 Java 容器
      • 11 Java 虚拟机
      • 12 Java IO
      • 13 Java HashMap
      • 14 Java List
      • 15 Java Stream
      • 16 Java 枚举
      • 17 Java 日期与时间
      • 18 Java fail fast
      • 19 Java BiFunction 和 BinaryOperator
    • 并发编程
      • 01 Java 并发
      • 02 synchronized
      • 03 volatile
      • 04 ReentrantLock
      • 05 ReadWriteLock
      • 06 StampedLock
      • 07 CompletableFuture
      • 08 ForkJoin
      • 09 ThreadLocal
      • 10 CountDownLatch
      • 11 ThreadPoolExecutor
      • 12 ExecutorService
      • 13 Atom 原子类
      • 14 BlockingQueue
    • 高效编程
      • 01 30 seconds of java8
      • 02 函数式替代 for 循环
      • 03 Java 字符串拼接
      • 04 单例模式的几种实现
      • 05 HashMap 排序
    • 理论概念
      • 01 Java Servlet
      • 02 Java 服务端分层模型
      • 03 经典排序算法
      • 04 LRU 缓存淘汰算法
      • 05 BloomFilter 判断元素存在
      • 06 Java HashMap 面试大全
      • 07 HTTP 状态码详解
      • 08 Cookie 和 Session
      • 09 基于消息队列的分布式事务解决方案
      • 10 微服务之所见
    • 实战系列
      • 01 AES CBC 加解密
      • 02 Magic 魔数获取文件类型
      • 03 获取请求 IP 地址
      • 04 Kaptcha 与数学公式验证码
      • 05 Netty 获取客户端 IP.md
      • 06 高性能无锁队列 Disruptor.md
      • 07 前后端接入阿里云盾
    • Linux
      • 01 Linux 文件权限系统
      • 02 Linux 常用软件安装
      • 03 CentOS 防火墙
    • MySQL
      • 01 MySQL
      • 02 SQL 语句 where 1=1
      • 03 truncate 和 delete
      • 04 事务
      • 05 关系模型
      • 06 Mybatis
      • 07 MySQL 查看数据库表详情
    • Nginx
      • 01 Nginx 指北
      • 02 nginx gzip 压缩
    • Note
      • 01 Vagrant
      • 02 Docker
      • 03 Lombok
      • 04 Swagger
      • 05 Redis
    • Spring
      • 01 Spring Boot
      • 02 Spring Validation
      • 03 Spring Data
      • 04 Spring 容器
      • 05 Spring AOP
      • 06 Spring Transactional 注解
      • 07 Spring Cloud Netflix
      • 08 Spring Cloud Alibaba
      • 09 Spring Security oAuth2
      • 10 Spring Boot 跨域解决方式
      • 11 Spring Boot 请求拦截
      • 12 Spring Boot 异步编程
      • 13 Spring Boot 定时任务
      • 14 Spring Boot 管理 bean
      • 15 Mybatis 逆向代码生成
      • 16 JWT
      • 17 JPA
      • 18 Apache Shiro
      • 19 Spring 异步请求
  • 书斋
    • ES6 标准入门
      • 01 变量声明与解构赋值
      • 02 语法的扩展
      • 03 数据类型与数据结构
      • 04 Proxy 和 Reflect
      • 05 异步编程 Promise
      • 06 Iterator 和 for of 循环
      • 07 Generator 函数
      • 08 Async 函数
      • 09 Class 类
    • JavaScript 设计模式
      • 01 基础知识
      • 02 设计模式(上)
      • 03 设计模式(下)
      • 04 设计原则和编程技巧
  • 纸函
    • 01 Interview
    • 02 Ceph
    • 03 动态规划
    • 04 Document.designMode
    • 2023-01-10
  • 万藏
    • 文档
      • 01 Git 文档
      • 02 Linux 命令大全
      • 03 七天学会 NodeJS
      • 04 Algorithms
    • 工具
      • 01 Nginx Config
      • 02 ProcessOn
      • 03 Flat Icon
      • 04 Regexper
      • 05 TempMail
      • 06 Carbon
由 GitBook 提供支持
在本页
  • async 函数
  • 含义
  • 语法

这有帮助吗?

  1. 书斋
  2. ES6 标准入门

08 Async 函数

Async 函数是 ES2017 标准提供的改进版异步编程解决方案,它比 Generator 函数更加优雅方便。Async 函数就是隧道尽头的亮光,很多人认为它是异步操作的终极解决方案。

异步操作是 JavaScript 编程的麻烦事,从最早的回调函数,到 Promise 对象,再到 Generator 函数,每次都有所改进,但又让人觉得不彻底。它们都有额外的复杂性,都需要理解抽象的底层运行机制。

异步编程的最高境界,就是根本不用关心它是不是异步。

async 函数

含义

总体来讲,async 函数是 Generatot 函数的语法糖,但是相比之有 4 点改进:

  1. 内置执行器:Generator 函数执行必须靠执行器,而 async 函数自带执行器。

  2. 更好的语义:async 和 await 相比星号和 yield,语义更清晰。

  3. 更广的适用性:yield 命令后面只能是 Thunk 函数或 Promise 对象,而 await 命令后面可以是 Promise 对象或原始值。

  4. 返回值是 Promise:async 函数返回值是 Promise 对象,而 Generator 函数返回值是 Iterator 对象。

async 函数可以看作由多个异步操作包装成的一个 Promise 对象,而 await 命令就是内部 then 命令的语法糖。

语法

await 命令

async 函数返回一个 Promise 对象。async 函数内部 return 语句返回的值,会成为 then 方法回调函数的参数。

正常情况下,await 命令后面是一个 Promise 对象。如果不是,会被转成一个立即 resolve 的 Promise 对象。await 命令后面的 Promise 对象如果变为 reject 状态,则 reject 的参数会被 catch 方法的回调函数接收到。此时加不加 return 效果一样。

async function f() {
  // 加不加 return 效果一样
  await Promise.reject('出错了')
}

f()
  .then(v => console.log(v))
  .catch(e => console.log(e)) // 出错了

需要注意:只要一个 await 语句后面的 Promise 变为 reject,那么整个 async 函数都会中断执行。如果希望异步操作失败也不会中断后面的异步操作,有两种解决办法:

  1. 将 await 放在 try...catch 结构里面。

  2. 在 await 后面的 Promise 对象后面添加一个 catch 方法。

async function f() {
  // 失败也能继续执行后面的异步操作
  await Promise.reject('出错了').catch(e => console.log(e))
  return await Promise.resolve('hello world')
}

错误处理

如果 await 后面的异步操作出错,那么等同于 async 函数返回的 Promise 对象被 reject。如果有多个 await 命令,则可以统一放在 try...catch 结构中。

// 使用 try...catch 实现多次重复尝试
const NUM_RETRIES = 3
async function test() {
  for (let i = 0; i < NUM_RETRIES; ++i) {
    try {
      await fetch('http://google.com/this-throws-an-error')
      // 如果请求成功,则跳出循环,否则继续重试直至三次
      break
    } catch (err) {}
  }
}

注意点

在使用 await 命令时,有几个注意点:

  1. 最好将 await 命令放在 try...catch 代码块中。

  2. 多个 await 命令如果不存在继发关系,最好同时触发。

// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()])

// 写法二
let fooPromise = getFoo()
let barPromise = getBar()
let foo = await fooPromise
let bar = await barPromise

forEach 陷阱

如果使用 forEach 循环执行异步操作,此时多个异步操作是并发执行的:

function func() {
  list.forEach(async url => {
    // 并发执行
    await fetch(url)
  })
}

正确的写法是采用 for 循环或 for of 循环:

async function func() {
  for (let url of list) {
    // 相继执行
    await fetch(url)
  }
}

为什么使用 forEach 和 for 循环执行多个异步操作会有不同表现,翻阅一些网上资料,找到 forEach 的 polyfill 实现如下:

Array.prototype.forEach = function(callback) {
  // this represents our array
  for (let index = 0; index < this.length; index++) {
    // We call the callback for each entry
    callback(this[index], index, this)
  }
}

可以看出相当于 for 循环执行了这个异步函数,但是却是并发执行的,因为 callback 函数并没有进行异步执行。我们可以改造一个异步执行的 forEach 函数:

async function asyncForEach(array, callback) {
  for (let index = 0; index < array.length; index++) {
    await callback(array[index], index, array)
  }
}

则可以改造上面的 forEach 异步操作代码:

asyncForEach(list, async url => {
  await fetch(url)
})

实际开发中经常遇到一组异步操作,需要按照顺序完成。比如,依次远程读取一组 URL,然后按照读取的顺序输出结果。分别使用 Promise 和 async 实现如下:

// Promise 实现
function logInOrder(urls) {
  // 远程读取所有 URL
  const textPromises = urls.map(url => {
    return fetch(url).then(response => response.text())
  })

  // 按次序输出
  textPromise.reduce((chain, textPrimose) => {
    return chain.then(() => textPromise).then(text => console.log(text))
  }, Promise.resolve())
}

// async 实现
function logInOrder(urls) {
  // 并发读取所有 URL
  const textPromises = urls.map(async url => {
    const response = await fetch(url)
    return response.text()
  })

  // 按次序输出
  for (const textPromise of textPromisces) {
    console.log(await textPromise)
  }
}

关于异步操作的重要概念之一遍历器这里按下不表,等有需要再来重新回顾。

上一页07 Generator 函数下一页09 Class 类

最后更新于2年前

这有帮助吗?