async函数

一.什么是async函数

1. 概念

async关键字将函数转为异步函数。

function hello() {
  return 'world';
}
hello(); //返回一个字符串

// async关键字将函数转为异步函数;异步函数将普通函数转为promise
async function hello() {
  return 'world';  
}
hello(); //返回一个promise对象

await关键字只能用于异步函数中。

async+await 等同于 generator+co

await和yield不同在于,yield不返回任何值;await返回后面Promise对象的返回值或者返回后面的原始数据。

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对象;如果是原始类型/普通对象的值,直接返回。

  如果对象是thenable对象,直接将其当作Promise对象处理。

4.await 等待后面的Promise对象返回结果。会拦截async函数内部的执行过程;async函数外部按照事件循环机制运行。

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-09 14:34:58
 * @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">
     // 要求前一个动画执行完再执行下一个;有一个动画出错就不再执行;
     // 成功返回最后一个返回值;动画失败返回上一个执行成功的返回值
     function createAnimate(ele, color) {// 实现每一秒改变一次颜色;定时器同时执行,所以每次差1s
       return new Promise((resolve, reject) => {
         setTimeout(function () {
           ele.style.backgroundColor = color;
           resolve(color);
         }, 1000)
       })
     }
     // const animations = ['0f0','f00','00f'].map(i => createAnimate(i));
     // 上面的代码会预加载;相当于并发执行
     async function chainAnimationsPromise(ele, animations) {
       let result;
       for(let color of animations) {
         // 想实现异步任务继发执行,要在await后面再生成Promise对象
         // 因为Promise生成时立即执行;否则会提前执行。
         result = await createAnimate(ele, color);
       }
       return result;
     }
     
     const animations = ['#0f0','#f00','#00f'];
     const rootElement = document.querySelector('#root');
     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-09 14:31:09
 * @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">
    let createAnimate = function (elem, color) {
      return new Promise((resolve, reject) => {
        setTimeout(function () {    
          elem.style.backgroundColor = color;    
          resolve(color)
        }, 1000)
      })
    }
    function chainAnimationsPromise(elem, animations) {     
      let result = null;// 变量result用来保存上一个动画的返回值
      let p = Promise.resolve();
      for (let color of animations) {
        p = p.then(function (val) {
          result = val;
          return createAnimate(elem, color);
        });
      }
      return p.catch(function (e) {
        /* 忽略错误,继续执行 */
      }).then(function (res) { //res是最后的成功值,有错误时是undefined;result是失败后上次的成功返回值
        return res || result;
      });
    }
    
    const rootElement = document.querySelector('#root');
    const animations = ['#f00', '#0f0', '#00f']; 
    chainAnimationsPromise(rootElement, animations).then(finalResult => {
      console.log(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

提案 : 目前浏览器支持;

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

问题场景:

// 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}

 应用:

await imports('/.....')

 

posted @ 2019-11-06 10:51  Lyra李  阅读(672)  评论(0编辑  收藏  举报