async函数解析
转载请注明出处:async函数解析
async函数是基于Generator函数实现的,也就是说是Generator函数的语法糖。在之前的文章有介绍过Generator函数语法和异步应用,如果对其不了解的,可以先看看关于Generator函数的文章,这样学习async函数的难度就不会太大。
传送门: Generator语法解析 Generator函数异步应用
接下来会用一些篇幅说明一下async函数,文末会给出async函数的参考学习文章。
文章目录
- 含义
- 基本语法
- 错误处理
- 异步应用
含义
我们知道,调用Generator函数不会立即执行,而是返回遍历器对象。疲于手动执行遍历器对象,因此就有了thunk(thunkify)函数结合run函数来实现自动流程管理。或者,使用co模块来实现自动流程管理,使Generator函数的使用更加方便。
而async函数ES2017标准引入的语法,是Generator函数的语法糖,因此其相对于Generator函数,具有以下基本特点。
内置执行器:使用async函数可以像使用普通函数一样,直接调用即可执行。不用像Generator函数一样使用co模块来实现流程控制。
语义化更强:async关键字表示是一个异步的函数,await表示需要等待执行。相对于yield表达式,语义化更强。
返回值是Promise:async函数返回值是Promise对象,这比Generator函数的返回值是Iterator对象方便多了,可以使用then方法来指定下一步的操作。
基本语法
使用async
关键字表明函数是一个async函数,内部使用await
关键字表明需要等待异步任务结束后才继续往下执行。
async function as () {
return 123
}
as().then(data => {
console.log(data)
})
从上面代码可以看出,调用async函数会返回Promise对象,返回值可以作为then方法成功处理函数的参数值。
如果在async内部如果抛出错误或者出现异常,会被then方法的错误处理函数捕获或者catch方法捕获。
async function as () {
throw new Error('出错拉!')
}
as().then(data => {
console.log(data)
}).catch(err => {
console.log(err)
}) // Error: xixi, catch方法捕获到错误
另外,async函数内部可以使用await关键字,表示后面的表达式是异步任务。await关键字后边的表达式可以是一个Promise对象,或者简单(复杂)数据类型(Number, String, RegExp, Boolean, Array, Objext)。如果是简单(复杂)数据类型,async函数会隐式调用Promise.resolve
方法将其转换为Pormise对象。
function foo () {
return new Promise((resolve, reject) => {
window.setTimeout(() => {
resolve('async')
}, 1000)
})
}
async function as () {
const data = await foo() //foo函数使用setTimeout来模拟异步。
console.log(data)
}
as() // async
async function as () {
return await 123 //如果是其他数据类型,也是如此。
}
as().then(data => {
console.log(data)
}) // 123
如果await关键字后面的表达式是非Promise、非thenable的普通的值,则会隐式调用Promise.resolve
方法将其转换为Promise对象,await关键字会在内部调用then方法将resolve的值返回。
await内部实现大致如下
function await (data) {
return new Promise((resolve, reject) => {
resolve(data)
}).then(data => {
return data
})
}
总之,await关键字是then方法的语法糖,会将resolve的值传递出来。
另外,如果在await关键字后的表达式抛出了错误,会使async函数返回的Promise对象从pending
状态转变为reject
状态,进而被catch方法捕获到错误。
function foo () {
throw new Error('err')
}
async function as () {
await foo()
}
as().then(data => {})
.catch(err => {
console.log(err);
}) // as函数返回的Promise对象从pending状态变为reject状态。
如果某个await关键字后面的表达式抛出错误,async函数的状态就会变为reject,那么函数就会暂停执行,后面的表达式就不会在继续执行。因为Promise函数有一个特点是,一旦状态改变,就不会再变,之后在调用也是保持同一个状态。
function foo () {
throw new Error('err')
}
async function as () {
await foo()
return Promise.resolve('succ') // 不会执行到这里,因为Promise对象的状态一旦改变就不会在变了,因此不执行。
}
as().then(data => {})
.catch(err => {
console.log(err);
})
因为async函数默认情况下返回的是Promise对象,因此可以将async函数作为await关键字后面的表达式。async函数调用另一个async函数会更加方便,不会像Generator函数需要使用yield*
表达式来调用。
async function foo () {
return Promise.resolve('async')
}
async function as () {
return await foo() // 调用foo函数会返回Promise对象
}
as().then(data => {
console.log(data)
})
另外,如果async函数内部没有抛出错误,函数正常执行。那么每一个await关键字后面的异步任务会继发执行。也就是说,一个异步任务结束之后才会执行另外一个异步任务,而不是并发执行。
async function foo () {
return new Promise((resolve, reject) => {
window.setTimeout(() => {
resolve(10)
}, 1000)
})
}
async function bar () {
return new Promise((resolve, reject) => {
window.setTimeout(() => {
resolve(20)
}, 2000)
})
}
async function as () {
let t1 = Date.now()
const a = await foo()
const b = await bar()
let t2 = Date.now()
console.log(t2 - t1) // 有误差,大概3004ms
return a + b
}
as().then(data => {
console.log(data) // 大概3s后输入30
})
如果两个异步任务互不依赖,如果按照上面的代码,两个异步任务继发执行,这样做的缺点是时间浪费了。本来200ms可以完成的两个异步任务,却用了400ms。因此可以让两个互不依赖的异步任务同时触发。有两种方法:
// 方法一:
async function as () {
const t1 = Date.now()
const [fo, ba] = [foo(), bar()]
// 以上两个函数同时执行,并将结果作为await关键字的表达式
const a = await fo
const b = await ba
const t2 = Date.now()
console.log(t2 - t1)
return a + b
}
// 写法二:结合使用Promise.all等待所有异步任务完成后才会返回
async function as () {
const t1 = Date.now()
const arr = await Promise.all([foo(), bar()])
const t2 = Date.now()
console.log(t2 - t1)
return arr[0] + arr[1]
}
as().then(data => {
console.log(data) // 30
})
错误处理
由于async函数内部的异步任务一旦出现错误,那么就等同于async函数返回的Promise对象被reject。因此,为了防止异步任务出现错误,可以使用try...catch
来捕获错误,使async函数内部可以正常执行。
async function as () {
let a = 0
let b = 0
try {
a = await foo()
b = await bar()
} catch (e) {}
return a + b
}
as().then(data => {
console.log(data) // 30
})
我们知道,try...catch
只能用于处理同步的操作,对于异步任务无法捕获到错误。而await关键字能够暂停函数处理,等待异步任务结束之后返回。因此在async函数中使用try...catch
结合await关键字捕获异步错误是一个不错的方法。
异步应用
我们来看看使用Promise、Generator、async来实现异步应用的差别。接下来会使用setTimeout
来模拟异步。
先来看两个基础函数
function foo (obj) {
return new Promise((resolve, reject) => {
window.setTimeout(() => {
let data = {
height: 180
}
data = Object.assign({}, obj, data)
resolve(data)
}, 1000)
})
}
function bar (obj) {
return new Promise((resolve, reject) => {
window.setTimeout(() => {
let data = {
talk () {
console.log(this.name, this.height);
}
}
data = Object.assign({}, obj, data)
resolve(data)
}, 1500)
})
}
两个函数内部都返回了Promise实例对象,通过Object.assign
来合并传递过来的参数。
首先看看纯Promise对象的实现。
function main () {
return new Promise((resolve, reject) => {
const data = {
name: 'keith'
}
resolve(data)
})
}
main().then(data => {
foo(data).then(res => {
bar(res).then(data => {
return data.talk() // keith 180
})
})
})
调用过程中就是在不断使用then方法,不够直观,操作本身的语义不太容易看出来。而且有可能出现回调地狱的风险。
接下来看看Generator函数的实现。由于Generator函数的调用需要手动执行,因此写了run函数来实现流程自动控制。
function *gen () {
const data = {
name: 'keith'
}
const fooData = yield foo(data)
const barData = yield bar(fooData)
return barData.talk()
}
function run (gen) {
const g = gen()
const next = data => {
let result = g.next(data)
if (result.done) return result.value
result.value.then(data => {
next(data)
})
}
next()
}
run(gen)
使用run函数来实现自动流程控制,Generator函数的好处相对于Promise对象来说,使得异步的过程同步化,同时少了回调地狱的风险。但是缺点是需要使用像run函数或者co模块来实现流程控制。
接下来使用async函数来实现看看。
async function main () {
const data = {
name: 'keith'
}
const fooData = await foo(data)
const barData = await bar(fooData)
return barData
}
main().then(data => {
data.talk()
})
从上面代码中,可以看出,使用async函数的代码量最少,而且使得异步过程同步化,更进一步,async函数内置执行器。调用的方法更加简洁。
ok,差不多就这样了,稍微总结一下。
- async函数是基于Generator函数实现的,是Generator函数的语法糖。其内置执行器,调用后返回Promise对象,因此可以像普通韩式一样使用。
- async函数内部抛出错误或者await关键字后面的表达式抛出错误,会使async函数返回的Promise对象从
pending
状态变为reject
状态,从而可以被catch方法捕获错误。而且,Promise对象的状态一旦改变就不会再变,之后的异步任务就不会执行了。 - await关键字后面的表达式可以是Promise对象,也可以是其他数据类型。如果是其他数据类型,则会通过
Promise.resolve
将其转换为Promise对象 - async函数内部如果有多个await关键字,其后的异步任务会继发执行。如果每一个异步任务不相互依赖,则可以使用
Promise.all
让其并发执行,这样可以在同样的时间里完成多个异步任务,提高函数执行效率。 - 对于async内部抛出的错误,可以使用
try...catch
来捕获异常。虽然try...catch
只能用于捕获同步任务,但是await关键字可以使得异步任务同步化,因此可以结合try...catch
和await关键字捕获异步任务。
参考资料:
async函数解析的更多相关文章
- [转]javascript eval函数解析json数据时为什加上圆括号eval("("+data+")")
javascript eval函数解析json数据时为什么 加上圆括号?为什么要 eval这里要添加 “("("+data+")");//”呢? 原因在于: ...
- async 函数学习笔记
async函数就是Generator函数的语法糖. var fs = require('fs'); var readFile = function (fileName) { return new Pr ...
- PHP json_decode 函数解析 json 结果为 NULL 的解决方法
在做网站 CMS 模块时,对于模块内容 content 字段,保存的是 json 格式的字符串,所以在后台进行模块内容的编辑操作 ( 取出保存的数据 ) 时,需要用到 json_decode() 函数 ...
- Matlab中bsxfun和unique函数解析
一.问题来源 来自于一份LSH代码,记录下来. 二.函数解析 2.1 bsxfun bsxfun是一个matlab自版本R2007a来就提供的一个函数,作用是”applies an element-b ...
- socket使用TCP协议时,send、recv函数解析以及TCP连接关闭的问题
Tcp协议本身是可靠的,并不等于应用程序用tcp发送数据就一定是可靠的.不管是否阻塞,send发送的大小,并不代表对端recv到多少的数据. 在阻塞模式下, send函数的过程是将应用程序请求发送的数 ...
- sigaction函数解析
http://blog.chinaunix.net/uid-1877180-id-3011232.html sigaction函数解析 sigaction函数的功能是检查或修改与指定信号相关联的处理 ...
- C# 5.0 Async函数的提示和技巧
一.创建Async函数 Async是C# 5.0中新增的关键字,通过语法糖的形式简化异步编程,它有如下三种方式: async Task<T> MyReturningMethod { ret ...
- driver_register()函数解析
driver_register()函数解析 /** * driver_register - register driver with bus * @drv: driver to register * ...
- async 函数
同步 console.log(1); console.log(2); console.log(3); console.log(4); //异步 ajax 文件读取io操作 console.log(1) ...
随机推荐
- 跟我一起,利用bitcms内容管理系统从0到1学习小程序开发:一、IIS下SSL环境搭建
缘起 1.从事互联网十来年了,一直想把自己的从事开发过程遇到的问题给写出来,分享给大家.可是可是这只是个种想法,想想之后就放下了,写出来的类文章是少之又少.古人说无志之人常立志,有志之人立长志.今天, ...
- python pandas stack和unstack函数
在用pandas进行数据重排时,经常用到stack和unstack两个函数.stack的意思是堆叠,堆积,unstack即"不要堆叠",我对两个函数是这样理解和区分的. 常见的数据 ...
- Python 开发个人微信号在运维开发中的使用
一.主题:Python 开发个人微信号在运维开发中的使用 二.内容: 企业公众号 介绍开发微信公众号的后台逻辑,包括服务器验证逻辑.用户认证逻辑 个人微信号 面对企业微信的种种限制,可以使用 Itch ...
- p2p 打洞技术
根据通信双方所处网络环境不同,点对点通信可以划分成以下三类:i> 公网:公网ii>公网:内网iii>内网:内网前两种容易实现,我们这里主要讨论第三种.这其中会涉及到NAT和NAPT的 ...
- SQLAlchemy基础操作一
用前安装 pip3 install sqlalchemy ORM ORM就是运用面向对象的知识,将数据库中的每个表对应一个类,将数据库表中的记录对应一个类的对象.将复杂的sql语句转换成类和对象的操作 ...
- EasyUI学习笔记---Datagrid真分页
EasyUI Datagrid组件在我看来功能还是很强大的,在我使用过程中遇到分页请求的问题困扰了一天才解决,下面我就把我遇到的问题分享一下 //datagrid数据表格渲染 $("#dg& ...
- 2、ABPZero系列教程之拼多多卖家工具 更改数据库为Mysql
因为要部署项目到云服务器,不想在服务器上装SqlServer,所以需要把项目改为Mysql. 项目初始化 1.下载项目压缩包,前面文章已经说到,可以加群到群文件里下载.解压缩下载的项目源码,使用VS2 ...
- CubeMX使用及感受
简介 CubeMX这几年刚流行起来,是一个STM32代码的初始化配置工具,里面封装了硬件层.中间层,以及示例代码. cube使用 该软件的安装需要较高版本jdk支持,固件库安装时需要注意和主程序的版本 ...
- CSS基础知识(颜色、伪类、盒子模型)
6.设置颜色单位 L 普通英文单词 {color : 属性值red;} 此方法简单,便捷.但设置的颜色在不同浏览器中,可能显示的颜色出现差异 * 三原色 - 红.绿.蓝 L 颜色的八进制方式 ...
- 11、ABPZero系列教程之拼多多卖家工具 拼团提醒功能页面实现
上一篇讲解了拼团提醒逻辑功能实现,现在继续实现页面功能. Core项目 打开AbpZeroTemplate-zh-CN.xml语言文件,在末尾添加如下代码: 文件路径:D:\abp version\a ...