async函数解析
2018-01-28 19:19
204 查看
转载请注明出处:async函数解析
async函数是基于Generator函数实现的,也就是说是Generator函数的语法糖。在之前的文章有介绍过Generator函数语法和异步应用,如果对其不了解的,可以先看看关于Generator函数的文章,这样学习async函数的难度就不会太大。
传送门: Generator语法解析 Generator函数异步应用
接下来会用一些篇幅说明一下async函数,文末会给出async函数的参考学习文章。
基本语法
错误处理
异步应用
而async函数ES2017标准引入的语法,是Generator函数的语法糖,因此其相对于Generator函数,具有以下基本特点。
内置执行器:使用async函数可以像使用普通函数一样,直接调用即可执行。不用像Generator函数一样使用co模块来实现流程控制。
语义化更强:async关键字表示是一个异步的函数,await表示需要等待执行。相对于yield表达式,语义化更强。
返回值是Promise:async函数返回值是Promise对象,这比Generator函数的返回值是Iterator对象方便多了,可以使用then方法来指定下一步的操作。
从上面代码可以看出,调用async函数会返回Promise对象,返回值可以作为then方法成功处理函数的参数值。
如果在async内部如果抛出错误或者出现异常,会被then方法的错误处理函数捕获或者catch方法捕获。
另外,async函数内部可以使用await关键字,表示后面的表达式是异步任务。await关键字后边的表达式可以是一个Promise对象,或者简单(复杂)数据类型(Number, String, RegExp, Boolean, Array, Objext)。如果是简单(复杂)数据类型,async函数会隐式调用
如果await关键字后面的表达式是非Promise、非thenable的普通的值,则会隐式调用
总之,await关键字是then方法的语法糖,会将resolve的值传递出来。
另外,如果在await关键字后的表达式抛出了错误,会使async函数返回的Promise对象从
如果某个await关键字后面的表达式抛出错误,async函数的状态就会变为reject,那么函数就会暂停执行,后面的表达式就不会在继续执行。因为Promise函数有一个特点是,一旦状态改变,就不会再变,之后在调用也是保持同一个状态。
因为async函数默认情况下返回的是Promise对象,因此可以将async函数作为await关键字后面的表达式。async函数调用另一个async函数会更加方便,不会像Generator函数需要使用
另外,如果async函数内部没有抛出错误,函数正常执行。那么每一个await关键字后面的异步任务会继发执行。也就是说,一个异步任务结束之后才会执行另外一个异步任务,而不是并发执行。
如果两个异步任务互不依赖,如果按照上面的代码,两个异步任务继发执行,这样做的缺点是时间浪费了。本来200ms可以完成的两个异步任务,却用了400ms。因此可以让两个互不依赖的异步任务同时触发。有两种方法:
我们知道,
先来看两个基础函数
两个函数内部都返回了Promise实例对象,通过
首先看看纯Promise对象的实现。
调用过程中就是在不断使用then方法,不够直观,操作本身的语义不太容易看出来。而且有可能出现回调地狱的风险。
接下来看看Generator函数的实现。由于Generator函数的调用需要手动执行,因此写了run函数来实现流程自动控制。
使用run函数来实现自动流程控制,Generator函数的好处相对于Promise对象来说,使得异步的过程同步化,同时少了回调地狱的风险。但是缺点是需要使用像run函数或者co模块来实现流程控制。
接下来使用async函数来实现看看。
从上面代码中,可以看出,使用async函数的代码量最少,而且使得异步过程同步化,更进一步,async函数内置执行器。调用的方法更加简洁。
ok,差不多就这样了,稍微总结一下。
async函数是基于Generator函数实现的,是Generator函数的语法糖。其内置执行器,调用后返回Promise对象,因此可以像普通韩式一样使用。
async函数内部抛出错误或者await关键字后面的表达式抛出错误,会使async函数返回的Promise对象从
await关键字后面的表达式可以是Promise对象,也可以是其他数据类型。如果是其他数据类型,则会通过
async函数内部如果有多个await关键字,其后的异步任务会继发执行。如果每一个异步任务不相互依赖,则可以使用
对于async内部抛出的错误,可以使用
参考资料:
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 函数
async 函数的含义和用法
相关文章推荐
- PHP中get_headers函数使用方法解析
- z-stack中的voidHalLedBlink (uint8 leds, uint8 numBlinks, uint8 percent, uint16 period)函数的深度解析
- (OpenCV)函数cv::partition解析
- 常用js函数收集、以及解析
- linux之umask函数解析
- execCommand()函数可用参数大解析
- TCP/IP网络编程函数解析
- MyBatis源码解析(二)——动态代理实现函数调用
- linux之unlink函数解析
- Discuz!常用函数解析(4)
- python-Levenshtein几个计算字串相似度的函数解析
- error LNK2019: 无法解析的外部符号 __imp__CryptCreateHash@20,该符号在函数 _MD5_Init 中被引用
- sigaction函数解析
- STL函数static void (* set_malloc_handler(void (*f)()))()与函数指针解析
- Discuz!常用函数解析(续)
- async 将异步解套,并传递参数给下一个函数
- underscore.js源码解析之函数绑定
- 解析PHP中ob_start()函数的用法
- PHP array_reduce()函数的应用解析
- 解析php addslashes()与addclashes()函数的区别和比较