《javascript高级程序设计》学习笔记 | 11.3.异步函数

2023-12-17 03:58

本文主要是介绍《javascript高级程序设计》学习笔记 | 11.3.异步函数,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

关注[前端小讴],原创技术文章

异步函数

  • ES8 新增异步函数(async/await),是 ES6 期约模式在 ECMAScript 函数中的应用
  • 同步方式的代码执行异步

相关代码 →

异步函数

  • ES8 对函数进行了扩展,新增 2 个关键字asyncawait

async

  • async关键字用于声明异步函数,可用在函数声明函数表达式箭头函数方法
async function foo() {} // 用在函数声明
let bar = async function () {} // 用在函数表达式
let baz = async () => {} // 用在箭头函数
class Qux {async qux() {} // 用在方法
}
  • async关键字让函数具有异步特性,代码仍同步求值,参数或闭包也具有普通 JS 函数的正常行为
async function foo() {console.log(1)
}
foo()
console.log(2)
/* 1,foo()函数先被求值2
*/
  • 异步函数return返回的值,会被Promise.resolve()包装成期约对象,调用异步函数始终返回该期约对象
    • return关键字返回的是实现thenable接口的对象(callback、期约),该对象由提供给then()的处理程序解包
    • return关键字返回的是常规的值,返回值被当作已解决的期约(无return关键字,返回值被当作 undefined)
async function foo() {return 'foo' // 返回原始值
}
console.log(foo()) // Promise {<fulfilled>: "foo"},被当作已解决的期约
foo().then((result) => console.log(result)) // 'foo'async function bar2() {return ['bar'] // 返回没有实现thenable接口的对象
}
console.log(bar2()) // Promise {<fulfilled>: ['bar']},被当作已解决的期约
bar2().then((result) => console.log(result)) // ['bar']async function baz2() {const thenable = {then(callback) {callback('baz')},}return thenable // 返回实现了thenable接口的非期约对象
}
console.log(baz2()) // Promise {<pending>}
baz2().then((result) => console.log(result)) // 'baz',由then()解包async function qux() {return Promise.resolve('qux') // 返回解决的期约
}
console.log(qux()) // Promise {<pending>}
qux().then((result) => console.log(result)) // 'qux',由then()解包async function rejectQux() {return Promise.reject('qux') // 返回拒绝的期约
}
console.log(rejectQux()) // Promise {<pending>}
rejectQux().then(null, (result) => console.log(result)) // 'qux',由then()解包
// Uncaught (in promise) qux
rejectQux().catch((result) => console.log(result)) // 'qux',由catch()解包
  • 异步函数中抛出错误会返回拒绝的期约
async function foo() {console.log(1)throw 3
}
foo().catch((result) => console.log(result)) // 给返回的期约添加拒绝处理程序
console.log(2)
/* 1,foo()函数先被求值23
*/
  • 异步函数中**拒绝期约的错误(非“返回拒绝的期约”)**不会被异步函数捕获
async function foo() {Promise.reject(3) // 拒绝的期约(非返回)
}
foo().catch((result) => console.log(result)) // catch()方法捕获不到
// Uncaught (in promise) 3,浏览器消息队列捕获

await

  • 使用await关键字可以暂停异步函数代码执行,等待期约解决
let p = new Promise((resolve, reject) => {setTimeout(resolve, 1000, 3)
})
p.then((x) => console.log(x)) // 3// 用async/await重写
async function foo() {let p = new Promise((resolve, reject) => {setTimeout(resolve, 1000, 3)})console.log(await p)
}
foo() // 3
  • await会尝试解包对象的值(与yield类似),然后将该值传给表达式,而后异步恢复执行异步函数
async function foo() {console.log(await Promise.resolve('foo')) // 将期约解包,再将值传给表达式
}
foo()async function bar2() {return await Promise.resolve('bar')
}
bar2().then((res) => console.log(res)) // 'bar'async function baz2() {await new Promise((resolve, reject) => {setTimeout(resolve, 1000)})console.log('baz')
}
baz2() // 'baz'(1000毫秒后)
  • await根据等待的值,执行不同的操作
    • 若等待的值是实现thenable接口的对象(callback、期约),该对象由await解包
    • 若等待的值是常规值,该值被当作已解决的期约(然后再由await来解包)
async function foo() {console.log(await 'foo') // 等待原始值,被当作已解决的期约Promise.resolve('foo'),再由await解包
}
foo() // 'foo'async function bar2() {console.log(await ['bar']) // 等待值是没有实现thenable接口的对象,被当作已解决的期约再由await解包
}
bar2() // ["bar"]async function baz2() {const thenable = {then(callback) {callback('baz')},}console.log(await thenable) // 等待值是实现了thenable接口的非期约对象,由await解包
}
baz2() // 'baz'async function qux() {console.log(await Promise.resolve('qux')) // 等待值是解决的期约
}
qux() // 'qux'
  • 等待会抛出错误的同步操作,会返回拒绝的期约
async function foo() {console.log(1)await (() => {throw 3 // 抛出错误的同步操作})()
}
foo().catch((result) => console.log(result)) // 给返回的期约添加拒绝处理程序
console.log(2)
/* 123
*/
  • 拒绝的期约使用await,会释放错误值(将拒绝期约返回)
async function foo() {console.log(1)await Promise.reject(3) // 对拒绝的期约使用await,将其返回(后续代码不再执行)console.log(4) // 不执行
}
foo().catch((result) => console.log(result)) // 给返回的期约添加拒绝处理程序
console.log(2)
/* 123
*/

await 的限制

  • 必须异步函数中使用
  • 不能在顶级上下文(如<script>标签或模块)中使用
  • 可以定义并立即调用异步函数
  • 异步函数的特质不会扩展到嵌套函数
async function foo() {console.log(await Promise.resolve(3)) // 必须在异步函数中使用
}
foo() // 3
;(async function () {console.log(await Promise.resolve(3)) // 3,立即调用的异步函数表达式
})()const syncFn = async () => {console.log(await Promise.resolve(3)) // 在箭头函数中使用,箭头函数前一样要加async
}
syncFn() // 3function foo() {// console.log(await Promise.resolve(3)) // 不允许在同步函数中使用
}async function foo() {// function bar() {//   console.log(await Promise.resolve(3)) // 错误:异步函数不会扩展到嵌套函数// }async function bar() {console.log(await Promise.resolve(3)) // 需要在bar前加async}
}

停止和恢复执行

  • async/await真正起作用的是awaitasync只是标识符)
    • JS 在运行时碰到await关键字,会记录在哪里暂停执行
    • 等到await右边的值可以用时,JS 向消息队列推送任务,该任务恢复异步函数的执行
    • 即使await右边跟着一个立即可用的值,函数也会暂停,且其余部分会被异步求值
// async只是标识符
async function foo() {console.log(2)
}
console.log(1)
foo()
console.log(3)
/* 123
*/// 遇到await -> 记录暂停 -> await右边的值可用 -> 恢复执行异步函数
async function foo() {console.log(2)await null // 暂停,且后续操作变为异步// 为立即可用的值null向消息队列中添加一个任务console.log(4)
}
console.log(1)
foo()
console.log(3)
/* 1234
*/
  • 如果await后面是一个期约,则会有两个任务被添加到消息队列并被异步求值
    • 第一个任务是等待期约的返回值,第二个任务是拿到返回值后执行进程
    • tc39 对await后面是期约的情况做过 1 次修改,await Promise.resolve()不再生成 2 个异步任务,而只是 1 个
async function foo() {console.log(2)console.log(await Promise.resolve(8))console.log(9)
}async function bar2() {console.log(4)console.log(await 6)console.log(7)
}console.log(1)
foo()
console.log(3)
bar2()
console.log(5)
/*书本顺序:1 2 3 4 5 6 7 8 9浏览器顺序:1 2 3 4 5 8 9 6 7(tc39做过1次修改)
*/

异步函数策略

实现 sleep()

  • 可以利用异步函数实现类似JAVAThread.sleep()的函数,在程序中加入非阻塞的暂停
function sleep(delay) {return new Promise((resolve) => setTimeout(resolve, delay)) // 设定延迟,延迟后返回一个解决的期约
}
async function foo() {const t0 = Date.now()await sleep(1500) // 暂停约1500毫秒console.log(Date.now() - t0)
}
foo() // 1507

利用平行执行

  • 按顺序等待 5 个随机的超时
async function randomDelay(id) {const delay = Math.random() * 1000 // 随机延迟0-1000毫秒return new Promise((resolve) =>setTimeout(() => {console.log(`${id} finished`)resolve()}, delay))
}async function foo() {const t0 = Date.now()await randomDelay(0)await randomDelay(1)await randomDelay(2)await randomDelay(3)await randomDelay(4)console.log(`${Date.now() - t0} ms elapsed`)
}
foo()
/* 0 finished1 finished2 finished3 finished4 finished3279 ms elapsed
*/// 用for循环重写
async function foo() {const t0 = Date.now()for (let i = 0; i < 5; i++) {await randomDelay(i)}console.log(`${Date.now() - t0} ms elapsed`)
}
foo()
/* 0 finished1 finished2 finished3 finished4 finished3314 ms elapsed
*/
  • 不考虑顺序时,可以先一次性初始化所有期约,分别等待结果(获得平行加速)
async function foo() {const t0 = Date.now()// 一次性初始化所有期约const p0 = randomDelay(0)const p1 = randomDelay(1)const p2 = randomDelay(2)const p3 = randomDelay(3)const p4 = randomDelay(4)// 分别等待结果,延迟各不相同await p0await p1await p2await p3await p4console.log(`${Date.now() - t0} ms elapsed`)
}
foo()
/* 4 finished3 finished1 finished0 finished2 finished870 ms elapsed,大幅度降低总耗时
*/// 用数组和for循环再次包装
async function foo() {const t0 = Date.now()const promises = Array(5).fill(null).map((item, i) => randomDelay(i))for (const p of promises) {await p}console.log(`${Date.now() - t0} ms elapsed`)
}
foo()
/* 1 finished3 finished0 finished4 finished2 finished806 ms elapsed
*/
  • 尽管期约未按顺序执行,但await按顺序收到每个期约的值
async function randomDelay(id) {const delay = Math.random() * 1000 // 随机延迟0-1000毫秒return new Promise((resolve) =>setTimeout(() => {console.log(`${id} finished`)resolve(id)}, delay))
}
async function foo() {const t0 = Date.now()const promises = Array(5).fill(null).map((item, i) => randomDelay(i))for (const p of promises) {console.log(`awaited ${await p}`)}console.log(`${Date.now() - t0} ms elapsed`)
}
foo()
/* 1 finished4 finished0 finishedawaited 0awaited 12 finishedawaited 23 finishedawaited 3awaited 4833 ms elapsed
*/

串行执行期约

  • 使用async/await期约连锁
function addTwo(x) {return x + 2
}
function addThree(x) {return x + 3
}
function addFive(x) {return x + 5
}
async function addTen(x) {for (const fn of [addTwo, addThree, addFive]) {x = await fn(x)}return x
}
addTen(9).then((res) => console.log(res)) // 19
  • 将函数改成异步函数,返回期约
async function addTwo(x) {return x + 2
}
async function addThree(x) {return x + 3
}
async function addFive(x) {return x + 5
}
addTen(9).then((res) => console.log(res)) // 19

栈追踪与内存管理

  • 超时处理执行拒绝期约时,错误信息包含嵌套函数的标识符(被调用以创建最初期约实例的函数)栈追踪信息中不应该看到这些已经返回的函数
    • JS 引擎会在创建期约时,尽可能保留完整的调用栈,抛出错误时栈追踪信息会占用内存,带来一些计算和存储成本
function fooPromiseExecutor(resolve, reject) {setTimeout(reject, 1000, 'bar')
}
function foo() {new Promise(fooPromiseExecutor)
}
foo()
/* Uncaught (in promise) barsetTimeout (async) // 错误信息包含嵌套函数的标识符fooPromiseExecutor // fooPromiseExecutor函数已返回,不应该在栈追踪信息中看到foo
*/
  • 换成异步函数,已经返回的函数不会出现在错误信息中,嵌套函数(在内存)中存储指向包含函数的指针,不会带来额外的消耗
async function foo() {await new Promise(fooPromiseExecutor)
}
foo()
/* Uncaught (in promise) barfooasync function (async)foo
*/

总结 & 问点

  • async 关键字的用法是什么?根据函数内返回值的不同,异步函数的返回值有哪些情况?
  • await 关键字的用法是什么?根据等待值的不同,调用异步函数有哪些情况?其使用有哪些限制?
  • JS 运行时遇到 await 关键字会怎样?函数的其余部分会在何时恢复执行?
  • 写一段代码,用异步函数实现在程序中加入非阻塞的暂停
  • 写一段代码,用异步函数平行执行多个期约,随机设定这些期约的延迟,并计算期约全部完成后的使用的时间
  • 写一段代码,用异步函数做期约连锁

这篇关于《javascript高级程序设计》学习笔记 | 11.3.异步函数的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/502977

相关文章

Java使用MethodHandle来替代反射,提高性能问题

《Java使用MethodHandle来替代反射,提高性能问题》:本文主要介绍Java使用MethodHandle来替代反射,提高性能问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑... 目录一、认识MethodHandle1、简介2、使用方式3、与反射的区别二、示例1、基本使用2、(重要)

Java实现本地缓存的常用方案介绍

《Java实现本地缓存的常用方案介绍》本地缓存的代表技术主要有HashMap,GuavaCache,Caffeine和Encahche,这篇文章主要来和大家聊聊java利用这些技术分别实现本地缓存的方... 目录本地缓存实现方式HashMapConcurrentHashMapGuava CacheCaffe

SpringBoot整合Sa-Token实现RBAC权限模型的过程解析

《SpringBoot整合Sa-Token实现RBAC权限模型的过程解析》:本文主要介绍SpringBoot整合Sa-Token实现RBAC权限模型的过程解析,本文给大家介绍的非常详细,对大家的学... 目录前言一、基础概念1.1 RBAC模型核心概念1.2 Sa-Token核心功能1.3 环境准备二、表结

Python函数返回多个值的多种方法小结

《Python函数返回多个值的多种方法小结》在Python中,函数通常用于封装一段代码,使其可以重复调用,有时,我们希望一个函数能够返回多个值,Python提供了几种不同的方法来实现这一点,需要的朋友... 目录一、使用元组(Tuple):二、使用列表(list)三、使用字典(Dictionary)四、 使

eclipse如何运行springboot项目

《eclipse如何运行springboot项目》:本文主要介绍eclipse如何运行springboot项目问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目js录当在eclipse启动spring boot项目时出现问题解决办法1.通过cmd命令行2.在ecl

Java中的Closeable接口及常见问题

《Java中的Closeable接口及常见问题》Closeable是Java中的一个标记接口,用于表示可以被关闭的对象,它定义了一个标准的方法来释放对象占用的系统资源,下面给大家介绍Java中的Clo... 目录1. Closeable接口概述2. 主要用途3. 实现类4. 使用方法5. 实现自定义Clos

Jvm sandbox mock机制的实践过程

《Jvmsandboxmock机制的实践过程》:本文主要介绍Jvmsandboxmock机制的实践过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、背景二、定义一个损坏的钟1、 Springboot工程中创建一个Clock类2、 添加一个Controller

SpringBoot实现文件记录日志及日志文件自动归档和压缩

《SpringBoot实现文件记录日志及日志文件自动归档和压缩》Logback是Java日志框架,通过Logger收集日志并经Appender输出至控制台、文件等,SpringBoot配置logbac... 目录1、什么是Logback2、SpringBoot实现文件记录日志,日志文件自动归档和压缩2.1、

MQTT SpringBoot整合实战教程

《MQTTSpringBoot整合实战教程》:本文主要介绍MQTTSpringBoot整合实战教程,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考... 目录MQTT-SpringBoot创建简单 SpringBoot 项目导入必须依赖增加MQTT相关配置编写

spring-gateway filters添加自定义过滤器实现流程分析(可插拔)

《spring-gatewayfilters添加自定义过滤器实现流程分析(可插拔)》:本文主要介绍spring-gatewayfilters添加自定义过滤器实现流程分析(可插拔),本文通过实例图... 目录需求背景需求拆解设计流程及作用域逻辑处理代码逻辑需求背景公司要求,通过公司网络代理访问的请求需要做请