async函数

匿名 (未验证) 提交于 2019-12-03 00:17:01

一.什么是async函数

1. 概念

async函数是co(Generator)函数的语法糖。

语法是将Generator函数的*替换为async; yield替换为await;

var fnName = async function() {   let result = await Promise.resolve(1);   console.log(result); //1 } fnName(); // 相当于 const co = require('co'); co(function* fnName() {   let result = yield Promise.resolve(1);   console.log(result); //1 })

async函数相当于执行器+Generator函数,原理如下:

async function fn() {    //.... } // 模拟源码实现 function fn() {   // run+Generator--等同于co + Generator   return run(function* (){    }) } function run(fn) {   return new Promise((resolve,reject) => {     const gen = fn();     function next(nextFn) {       let next;       try {         next = nextFn(); //相当于所有的gen.next()方法在try代码块中执行       } catch (error) {         reject(error);       }       if (next.done) {         resolve(next.value); // async函数生成的Promise对象的resolved状态是所有的await/yield命令完成, done:true时的值       }        Promise.resolve(next.value).then(data => { // await后面不是Promise对象,会被当作Promise对象处理                next(function() {return gen.next(data)})// data的值即await返回的值,yield表达式的值       }).catch(error => {         next(function() {return gen.throw(error)})       })     }     next(function() {return gen.next()}); // 不直接使用gen.next, 可能直接报错   }) }
async原理

2. 特征

1. 比Generator函数的语法更语义化

2. async函数执行返回一个Promise对象。co()方法也返回一个Promise对象。

返回的Promise对象状态会等所有await表达式运行完或者遇到return/throw后才会发生变化触发then方法。

async函数的return值,作为async函数调用后生成的Promise对象的回调函数的参数

var fnName = async function() {   let result = await Promise.resolve(1);   return 2; } const p = fnName(); p.then(result => {   console.log(result); // 2  等于done=true时的返回值;一般是return的值,否则undefined }).catch(() => {});

应用:

async函数返回Promise对象最直观的应用是作为map的回调函数

const result = [1,2,3].map(async () => {   await Promise.resolve('sth');   return 5; }); // map遍历后的值=回调函数的返回值 // async函数的返回值是Promise对象  console.log('result-->',result); Promise.all(result).then(data => console.log('data-->',data)) // 运行结果是 result-->[Promise,Promise,Promise] data-->[5,5,5]  

3.await 后面应该是Promise对象;如果是原始类型/普通对象的值,默认调用Promise.resolve()生成Promise对象。

Promise.resolve方法。

4.await 命令相当于调用Promise对象的then方法。

5. 如果函数内部没有await命令;可以当作普通函数来确认执行顺序。

3. 使用形式

// 函数声明 async function fn() {   //await Promise对象   // ... } // 函数表达式 const fn = async function() {   //... } // 对象方法 const obj = {   async fn() {   } } // 类方法 class A {   constructor() {   }   async fn() {    } } // 箭头函数 const fn = async () => {}// 回调函数[1,2,3].map(async () => {   // ...})

4. 保留运行堆栈和上下文环境

function c() {   throw new Error('err') } // a,b函数的功能都是Promise改变状态之后运行c() function a() {   const p = Promise.resolve(1);   console.log('a');   p.then(res => {     console.log('a-->',res);     c(); //执行到此处时,a()函数运行结束,上下文环境消失;错误堆栈不包含a   }) } async function b() {   console.log('b');   const res = await Promise.resolve(2);   console.log('b-->',res);   c(); //await暂停执行,保留上下文环境;错误堆栈会包含a } a(); b();

知识点: then方法可能会丢失上下文环境;await相当于调用then方法;会进入微任务队列

运行结果如下:

二. async函数生成的Promise对象的状态

1. resolved״̬

1. 内部不抛出错误;且内部的Promise对象没有rejected状态

根据原理代码可知,只有当内部遍历器的状态{value: returnValue, done: true}时,状态变为resolved状态,

返回此时对应的value值,该值作为成功回调函数的参数。

要想done=true,则所有的await对应的Promise对象都执行完成(源码内对应yield命令执行完成)。

2. 内部抛出异常;但是进行了捕获;状态为resolved状态

async function test() {   try {     console.log('--1--');     await Promise.reject(1);     console.log('--2--');     await Promise.resolve(2);   } catch (err) {     console.log('err-->',err)   }   console.log('--3--'); } test().then(result => {   console.log('result-->',result); }).catch(err => {   console.log('err-->',err) }) // 运行结果如下: // --1-- // err-->1 // --3-- // result-->undefined

2. rejected״̬

只要async函数执行过程中出现一个未捕获的错误,整个Promise对象的状态是rejected。

1. 函数内部手动抛出异常

async function test() {   throw new Error('err'); // throw自动触发状态为rejected } const p = test(); p.then(   data => console.log('data-->',data) //永不执行 ).catch(   err => console.log('err-->',err) ) // 运行结果: err-->Error:err

2. 函数内部所有await对应的Promise对象的状态只要出现一次rejected;

就会抛出异常(参考原理)

async function test() {   console.log('--1--');   await Promise.resolve('A');   console.log('--2--');   await Promise.reject('B'); //抛出异常,未捕获之后的代码不再执行   console.log('--3--')   await 'C'; } test().then(result => {   console.log('result-->',result); //不执行 }).catch(err => {   console.log('err-->',err); }); // 运行结果如下 --1-- --2--  err-->B

所以为了代码的可执行性,await命令应该全部放在try...catch代码块中(推荐,一个try...catch就可以)。

或者await后的Promise对象使用catch方法(不推荐,需要每个都写)

三. 应用

1. 实现继发(串行)异步操作--上一个结束再开始下一个

async实现继发异步操作(最简单的实现方式)

<!--  * @Author: LyraLee  * @Date: 2019-10-30 08:53:28  * @LastEditTime: 2019-11-06 00:36:57  * @Description: 串行异步操作  --> <!DOCTYPE html> <html lang="en">   <head>     <meta charset="UTF-8">     <meta name="viewport" content="width=device-width, initial-scale=1.0">     <meta http-equiv="X-UA-Compatible" content="ie=edge">     <title>串行异步操作</title>     <style>       #root{         width: 100px;         height: 100px;         background-color: #eee;       }     </style>   </head>   <body>     <div id="root"></div>     <script type="module">       // 要求前一个动画执行完再执行下一个;有一个动画出错就不再执行;       // 成功返回最后一个返回值;动画失败返回上一个执行成功的返回值       const rootElement = document.querySelector('#root');       const animations = ['#f00', '#0f0', '#00f'].map((color, index) => (function(ele) {         setTimeout(() => {           ele.style.backgroundColor = color;         }, 1000*(1 + index)) // 实现每一秒改变一次颜色;定时器同时执行,所以每次差1s         return color;       }))       async function chainAnimationsPromise() {         let result;         try {           for(let animate of animations) {             result = await animate(rootElement);           }                  } catch {}         return result;       }       chainAnimationsPromise(rootElement, animations).then(finalResult => {         console.log('final-->', finalResult)       });     </script>   </body> </html>
View Code

promise实现继发异步操作--代码逻辑比async复杂;代码量比async大

<!--  * @Author: LyraLee  * @Date: 2019-10-30 08:53:28  * @LastEditTime: 2019-11-06 00:28:54  * @Description: 串行异步操作  --> <!DOCTYPE html> <html lang="en">   <head>     <meta charset="UTF-8">     <meta name="viewport" content="width=device-width, initial-scale=1.0">     <meta http-equiv="X-UA-Compatible" content="ie=edge">     <title>串行异步操作</title>     <style>       #root{         width: 100px;         height: 100px;         background-color: #eee;       }     </style>   </head>   <body>     <div id="root"></div>     <script type="module">       // 要求前一个动画执行完再执行下一个;有一个动画出错就不再执行;       // 成功返回最后一个返回值;动画失败返回上一个执行成功的返回值       const rootElement = document.querySelector('#root');       const animations = ['#f00', '#0f0', '#00f'].map((color, index) => (function(ele) {         setTimeout(() => {           ele.style.backgroundColor = color;         }, 1000*(1 + index)) // 实现每一秒改变一次颜色;定时器同时执行,所以每次差1s         return color;       }))       function chainAnimationsPromise(elem, animations) {         // 变量ret用来保存上一个动画的返回值         let result = null;         // 新建一个空的Promise         let p = Promise.resolve();         // 使用then方法,添加所有动画         for(let animate of animations) {           p = p.then(function(val) {             console.log(val); //返回值取到倒数第二个动画的返回值             result = val;             return animate(elem);           });         }         // 返回一个部署了错误捕捉机制的Promise         return p.catch(function(e) {           /* 忽略错误,继续执行 */         }).then(function(res) { //res是最后的成功值,有错误时是undefined;result是失败后上次的成功返回值           return res || result;         });       }       chainAnimationsPromise(rootElement, animations).then(finalResult => {         console.log('final-->', finalResult)       });     </script>   </body> </html>
View Code

2. 实现并发异步操作

异步操作彼此之间独立,没有相互依赖关系,应该使用并发操作;可以大大降低执行时间。

语法:

async function fn() {    await Promise.all([promise1, promise2,....]) //并发执行;彼此独立 }

示例(浏览器环境)

function fetch() {   return Promise.resolve({     text() {       return new Promise((resolve,reject) => {         setTimeout(() => resolve('ok'),1000);       });     }   }) }  const url1 = 'https://api.github.com/users/github/orgs'; const url2 = 'https://api.github.com/users/ANTON072/subscriptions'; const urls = [url1, url2];  /***************************************  * 并发执行  * 执行结果之间彼此独立,互不依赖  * 输出结果也没有顺序要求  * ************************************/ async function test() {   console.time(2);   await Promise.all(urls.map(url => fetch(url).then(res => res.text())))   console.timeEnd(2) } console.time('1'); test();  //主要是为了说明函数的执行时间是同步任务的执行时间 console.timeEnd('1');

执行结果如下:

1: 0.18408203125ms 2: 1000.796875ms

比对继发执行的效果:

/***************************************  * 继发执行  * 当前的执行结果依赖于上一次的执行结果  * *************************************/ async function test1() {   console.time(3);   for(let url of urls) { // 继发执行     await fetch(url).then(res => res.text())   }   console.timeEnd(3) } test1();

运行结果如下:

3: 2003.61083984375ms

由结果可知:并发执行快于继发执行。

3. 实现按照顺序完成异步操作

异步操作彼此之间没有依赖关系;但是要求输出结果按照某种顺序(入参顺序)

/***************************************  * 顺序执行--Promise实现  * 执行结果之间彼此独立,  * 但是输出结果要求按照入参顺序  * *************************************/ function test2() {   // urls遍历时已经触发并发异步操作   console.time(4);   const promises = urls.map(url => fetch(url).then(res => res.text()));   promises.reduce((demo, promise) => {     console.log('reduce--')     return demo.then(() => {       console.log('--then promise--')       return promise}).then(data => console.log('promise order-->',data))   }, Promise.resolve()).then(() =>  console.timeEnd(4)) } test2();  /***************************************  * 顺序执行--async函数实现  * 执行结果之间彼此独立,  * 但是输出结果要求按照入参顺序  * *************************************/ async function test3() {   console.time(5);   const promises = urls.map(url => fetch(url).then(res => res.text()));   try {     let result;     for(let promise of promises) {       console.log('for--')       result = await promise;       console.log('async order-->',result)     }       } catch(error){     console.log('err-->',error)   }   console.timeEnd(5) } test3();  // 上面的log是为了联系方式异步方法的执行顺序;

reduce-- reduce-- for-- --then promise-- promise order-->ok --then promise-- promise order-->ok 4: 1004.158935546875ms async order-->ok for-- async order-->ok 5:1004.30810546875ms

虽然效果相似,但是async代码更简洁明了。

提案

目的: 解决异步模块加载问题

问题场景:

// await.js 被加载模块代码如下 let output; async function main() {     const dynamic = await import(url1);     const data = await fetch(url2);     // output的值依赖上面结果;继发执行     output = process(dynamic.default, data);  }main(); export { output }

其他模块代码引用该文件,因为main是异步方法,所以引用时可能未执行完成;返回undefined

现行解决方法:

// async函数执行返回一个promise export default main(); export {output}

使用时:

import promise, {output} from './await.js';  promise.then(() => { //确保async函数执行完成    //  使用output })

使用顶层await解决方案:

let output; const dynamic = import(url1); const data = fetch(url2); // output的值依赖上面结果;继发执行 output = process(await(dynamic).default, await data);  export {output}

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!