导论:
首先,必须了解Promise
主要研究基本语法
对比Promise与Async
异常处理
参考:
Async +Await
理解 async/await
异步函数 - 提高 Promise 的易用性
ES7 提出的async
函数,终于让 JavaScript 对于异步操作有了终极解决方案。No more callback hell。
async
函数是 Generator
函数的语法糖。使用 关键字 async
来表示,在函数内部使用 await
来表示异步。
想较于 Generator,Async
函数的改进在于下面四点:
Aysnc
函数自带执行器,调用方式跟普通函数的调用一样async
和 await
相较于 *
和 yield
更加语义化co
模块约定,yield
命令后面只能是 Thunk 函数或 Promise对象。而 async
函数的 await
命令后面则可以是 Promise 或者 原始类型的值(Number,string,boolean,但这时等同于同步操作)async
函数返回值是 Promise 对象,比 Generator 函数返回的 Iterator 对象方便,可以直接使用 then()
方法进行调用基本语法是 方法头 添加关键字async
,在异步前 添加await
核心 API 就async 与 await,具体 直接将MDN中解释拿来用
async function
声明将定义一个返回 AsyncFunction
对象的异步函数。异步函数是指通过事件循环异步执行的函数,它会通过一个隐式的 Promise
返回其结果。但是如果你的代码使用了异步函数,它的语法和结构会更像是标准的同步函数。 MDN
白话:async 返回一个 Promise
,也就是 最后return是不是 Promise 最终都会被包装成promise
await
操作符用于等待一个Promise
对象。它只能在异步函数 async function
中使用。MDN
//语法
[return_value] = await expression; //注意,返回并不是一个Promise对象,而是结果
表达式:
一个 Promise 对象或者任何要等待的值。
返回值: (注意,返回并不是一个Promise对象,而是结果)
返回 Promise 对象的处理结果。
如果等待的不是 Promise 对象,则返回该值本身。
描述:
await 表达式会暂停当前 async function
的执行,等待 Promise 处理完成。若 Promise 正常处理(fulfilled),其回调的resolve函数参数作为 await 表达式的值,继续执行 async function
。
若 Promise 处理异常(rejected),await 表达式会把 Promise 的异常原因抛出。
另外,如果 await 操作符后的表达式的值不是一个 Promise,那么该值将被转换为一个已正常处理的 Promise。
这会使 async 函数暂停执行,等待表达式中的 Promise 解析完成后继续执行 async 函数并返回解决结果。
//用法1
/*
async 返回一个 Promise
1. return 值(value),则返回 Promise.resolve(value)
2. 异常,则是 Promise.reject(err);
*/
async function testAsync() {
return "hello async";
}
const result = testAsync();
console.log(result);//返回一个promise对象
//用法2
//async 函数中可能会有 await 表达式,这会使 async 函数暂停执行,等待表达式中的 Promise 解析完成后继续执行 async 函数并返回解决结果。
function timeout(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
return value; //类似 return Promise.resolve(value)
}
//async 返回一个promise
asyncPrint('hello world', 50).then(function(d){
console.log('then',d);
});
/** 打印
hello world
then hello world
*/
//await 必须的在 async方法内,否则会报错
function timeout(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
return value; //类似 return Promise.resolve(value)
}
//async 返回一个promise
asyncPrint('hello world', 50).then(function(d){
console.log('then',d);
});
//Uncaught SyntaxError: await is only valid in async function
参考:理解 JavaScript 的 async/await
假设:假设一个业务,分多个步骤完成,每个步骤都是异步的,而且依赖于上一个步骤的结果。我们仍然用 setTimeout
来模拟异步操作:
/**
* 传入参数 n,表示这个函数执行的时间(毫秒)
* 执行的结果是 n + 200,这个值将用于下一步骤
*/
function takeLongTime(n) {
return new Promise(resolve => {
setTimeout(() => resolve(n + 200), n);
});
}
function step1(n) {
console.log(`step1 with ${n}`);
return takeLongTime(n);
}
function step2(n) {
console.log(`step2 with ${n}`);
return takeLongTime(n);
}
function step3(n) {
console.log(`step3 with ${n}`);
return takeLongTime(n);
}
//Promise方案
function doIt() {
console.time("doIt");
const time1 = 300;
step1(time1)
.then(time2 => step2(time2))
.then(time3 => step3(time3))
.then(result => {
console.log(`result is ${result}`);
console.timeEnd("doIt");
});
}
doIt();
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
//async 写法
//对比 promise写法,
async function doIt() {
console.time("doIt");
const time1 = 300;
const time2 = await step1(time1);
const time3 = await step2(time2);
const result = await step3(time3);
console.log(`result is ${result}`);
console.timeEnd("doIt");
}
doIt();
//promise 版本
function getProcessedData(url) {
return downloadData(url) // returns a promise
.catch(e => {
return downloadFallbackData(url) // 返回一个 promise 对象
.then(v => {
return processDataInWorker(v); // 返回一个 promise 对象
});
})
.then(v => {
return processDataInWorker(v); // 返回一个 promise 对象
});
}
//Async 版本
async function getProcessedData(url) {
let v;
try {
v = await downloadData(url);
} catch (e) {
v = await downloadFallbackData(url);
}
return processDataInWorker(v);
}
//注意,在上述示例中,return 语句中没有 await 操作符,因为 async function 的返回值将隐式传递给 Promise.resolve。
对比 promise 并行处理
前面解决都是 一个promise执行完后,再执行新的promise;而下面讨论是,两个Promise如何并行
// 方法 1
let [res1, res2] = await Promise.all([func1(), func2()])
// 方法 2
let func1Promise = func1()
let func2Promise = func2()
let res1 = await func1Promise
let res2 = await func2Promise
上文基本的并行,并不是 正在的并行
参考:MDN 下面代码参考自 MDN
var resolveAfter2Seconds = function() {
console.log("starting slow promise");
return new Promise(resolve => {
setTimeout(function() {
resolve(20);
console.log("slow promise is done");
}, 2000);
});
};
var resolveAfter1Second = function() {
console.log("starting fast promise");
return new Promise(resolve => {
setTimeout(function() {
resolve(10);
console.log("fast promise is done");
}, 1000);
});
};
var sequentialStart = async function() {
console.log('==SEQUENTIAL START==');
// 如果 await 操作符后的表达式不是一个 Promise 对象, 则它会被转换成一个 resolved 状态的 Promise 对象
const slow = await resolveAfter2Seconds();
const fast = await resolveAfter1Second();
console.log(slow);
console.log(fast);
}
var concurrentStart = async function() {
console.log('==CONCURRENT START with await==');
const slow = resolveAfter2Seconds(); // 立即启动计时器
const fast = resolveAfter1Second();
console.log(await slow);
console.log(await fast); // 等待 slow 完成, fast 也已经完成。
}
var stillSerial = function() {
console.log('==CONCURRENT START with Promise.all==');
Promise.all([resolveAfter2Seconds(), resolveAfter1Second()]).then(([slow, fast]) => {
console.log(slow);
console.log(fast);
});
}
var parallel = function() {
console.log('==PARALLEL with Promise.then==');
resolveAfter2Seconds().then((message)=>console.log(message)); // in this case could be simply written as console.log(resolveAfter2Seconds());
resolveAfter1Second().then((message)=>console.log(message));
}
sequentialStart(); // sequentialStart 总共花了 2+1 秒
// 等到 sequentialStart() 完成
setTimeout(concurrentStart, 4000); // concurrentStart 总共花了 2 秒
// 等到 setTimeout(concurrentStart, 4000) 完成
setTimeout(stillSerial, 7000); // stillSerial 总共花了 2 秒
// 等到 setTimeout(stillSerial, 7000) 完成
setTimeout(parallel, 10000); // 真正的并行运行
上面代码是4中,不同处理promise并行方式。但核心是不管怎样 await
执行都会有顺序,会等待执行。
//不推荐,因为是串行解决
function fetch(d){
return new Promise((resolve)=>{
console.log('start:',d);
setTimeout(()=>{resolve(d)},Math.random()*1000);
})
}
var args = [1,2,3,4,5];
async function test(args){
for(const arg of args){
const res = await fetch(arg);
console.log('end:',res);
}
}
test(args);
/**
start: 1
23:00:11.331 bundle.9303569f0937a02f1c80.js:4 end: 1
23:00:11.332 bundle.9303569f0937a02f1c80.js:4 start: 2
23:00:12.009 bundle.9303569f0937a02f1c80.js:4 end: 2
23:00:12.009 bundle.9303569f0937a02f1c80.js:4 start: 3
23:00:12.248 bundle.9303569f0937a02f1c80.js:4 end: 3
23:00:12.248 bundle.9303569f0937a02f1c80.js:4 start: 4
23:00:12.984 bundle.9303569f0937a02f1c80.js:4 end: 4
23:00:12.984 bundle.9303569f0937a02f1c80.js:4 start: 5
23:00:13.184 bundle.9303569f0937a02f1c80.js:4 end: 5
*/
function fetch(d){
return new Promise((resolve)=>{
console.log('start:',d);
setTimeout(()=>{resolve(d)},Math.random()*1000);
})
}
var args = [1,2,3,4,5];
async function test3(args){
const promises = args.map(async arg=>{//map 执行 可以并行执行
const re = await fetch(arg);
return re;
})
for(const p of promises){
p.then((d)=>{
console.log('end',d);
})
}
}
test3(args);
/**
start: 1
22:56:44.421 VM6500:3 start: 2
22:56:44.421 VM6500:3 start: 3
22:56:44.422 VM6500:3 start: 4
22:56:44.422 VM6500:3 start: 5
22:56:44.436 Promise {: undefined}
22:56:44.462 VM6500:15 end 2
22:56:44.552 VM6500:15 end 1
22:56:44.569 VM6500:15 end 5
22:56:44.974 VM6500:15 end 3
22:56:44.993 VM6500:15 end 4
*/
Array.prototype.map
与Array.prototype.forEach
执行promise数组,是并行。
但for-in
for-of
for
都是串行的
参考:Promise异常分类
异常简单分为分为 执行异常和异步异常(通过是否能try-catch捕获来区分);
//套路1
async function f() {
await new Promise(function (resolve, reject) {
throw new Error('出错了');
});
await Promise.resolve('1')
}
f()
.then(v => console.log(v))
.catch(e => console.log(e))
//套路2
async function f() {
try {
await new Promise(function (resolve, reject) {
throw new Error('出错了');
});
} catch(e) {
console.log(e)
}
}
f()
//为什么 Promise 无法使用try-catch捕获异常,但 async中,可以捕获?
//猜测可能是,await返回是一个值,执行上下文应该是同一个
//在基本套路1 基础上处理 与promise 链式异常处理对比
async function f() {
await new Promise(function (resolve, reject) {
console.log('1')
throw new Error('出错了');
});
await new Promise(function(resolve, reject){
console.log('2');//没有打印
resolve(2);
})
}
f()
.then(v => console.log(v))
.catch(e => console.log(e))
/**
1
Error: 出错了
at :4:11
at new Promise ()
at f (:2:9)
at :12:1
*/
重点: 第二个 await没有执行,(‘2’没有打印);也就证明,async
遇到异常 就会中断链,与Promise链式异常对比
//在基本套路2 基础上处理 对比
async function f() {
try {
await new Promise(function (resolve, reject) {
console.log('222');
throw new Error('出错了');
});
await new Promise(function (resolve, reject) {
console.log('222');//不会打印
resolve(222);
})
} catch(e) {
console.log(e)
}
}
f()
//与上例 是一样 不会执行第二个 await;
参见:github
async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。
async function fn(args) {
// ...
}
// 等同于
function fn(args) {
return spawn(function* () {
// ...
});
}
//generator 没有搞明白,直接是copy代码
function spawn(genF) {
return new Promise(function(resolve, reject) {
const gen = genF();
function step(nextF) {
let next;
try {
next = nextF();
} catch(e) {
return reject(e);
}
if(next.done) {
return resolve(next.value);
}
Promise.resolve(next.value).then(function(v) {
step(function() { return gen.next(v); });
}, function(e) {
step(function() { return gen.throw(e); });
});
}
step(function() { return gen.next(undefined); });
});
}
//await 与 async 中间隔了一个function
async function dbFuc(db) {
let docs = [{}, {}, {}];
// 报错
docs.forEach(function (doc) {
// 改成 docs.forEach( await function (doc) 就没毛病
await db.post(doc);
});
}
先定义一个 Fetch 方法用于获取 github user 的信息:
function fetchUser() {
return new Promise((resolve, reject) => {
fetch('https://api.github.com/users/superman66')
.then((data) => {
resolve(data.json());
}, (error) => {
reject(error);
})
});
Promise 方式
/**
* Promise 方式
*/
function getUserByPromise() {
fetchUser()
.then((data) => {
console.log(data);
}, (error) => {
console.log(error);
})
}
getUserByPromise();
Promise 的方式虽然解决了 callback hell,但是这种方式充满了 Promise的 then()
方法,如果处理流程复杂的话,整段代码将充满 then
。语义化不明显,代码流程不能很好的表示执行流程。
Generator 方式
/**
* Generator 方式
*/
function* fetchUserByGenerator() {
const user = yield fetchUser();
return user;
}
const g = fetchUserByGenerator();
const result = g.next().value;
result.then((v) => {
console.log(v);
}, (error) => {
console.log(error);
})
Generator 的方式解决了 Promise 的一些问题,流程更加直观、语义化。但是 Generator 的问题在于,函数的执行需要依靠执行器,每次都需要通过 g.next()
的方式去执行。
async 方式
/**
* async 方式
*/
async function getUserByAsync(){
let user = await fetchUser();
return user;
}
getUserByAsync()
.then(v => console.log(v));
async
函数完美的解决了上面两种方式的问题。流程清晰,直观、语义明显。操作异步流程就如同操作同步流程。同时 async
函数自带执行器,执行的时候无需手动加载。
更多前端技术文章
、美术设计
、wordpress插件、优化教程
、学习笔记
尽在我的个人博客喵容 - 和你一起描绘生活,欢迎一起交流学习,一起进步:https://www.miaoroom.com
「前端进阶」史上最全的前端学习路线
「不要重复造轮子系列」 前端常用插件、工具类库汇总
如何保障前端项目代码质量
记录一次基于vue、typescript、pwa的项目由开发到部署
小程序挖坑之路
原文链接:「前端进阶」完全吃透async/await,深入JavaScript异步