一.什么是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, 可能直接报错
})
}
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对象。
如果对象是thenable对象,那么参照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>
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>
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}
