—async和await是干什么用的—
一句话:是用来解决异步流程问题的。
避免回调地狱(callback hell),且以最接近同步代码的方式编写异步代码
async/await是对co模块的升级,内置生成器函数的执行器,不再依赖co模块。
—语义—
async 可以声明一个异步函数,此函数需要返回一个 Promise 对象。await可以等待一个 Promise 对象 resolve,并拿到结果。
function aaa(){
return new Promise((resolve) => {//返回一个promise对象
setTimeout(function(){//这里使用计时器模拟异步,也可以使用ajax,node异步读写等。。
resolve(123); //异步执行resolve函数
},2000)
})
}
async function getaaa(){
let a=await aaa();//等待aaa函数的执行结果
console.log(a)//拿到执行的返回值
}
getaaa()//调用这个函数
—小试牛刀—
- 一般用法(模拟同步写法)
var sleep = function (time) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve();//也可以把resolve写在sjax的sussess的回调中,都是异步调取
}, time);
})
};
var start = async function () {
// 在这里使用起来就像同步代码那样直观
console.log('start');//立即打印start
await sleep(3000);//等待三秒
console.log('end');//三秒后打印end
};
start();
- async函数捕捉异常(也可以用promise对象链上的catch方法)
var sleep = function (time) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
// 模拟出错了,返回 ‘error’
reject('error');
}, time);
})
};
var start = async function () {
try {
console.log('start');
await sleep(3000); // 这里得到了一个返回错误
// 所以以下代码不会被执行了,会走catch
console.log('end');
} catch (err) {
console.log(err); // 这里捕捉到错误 `error`
}
};
- 并行(依赖于Promise.all方法)
let count = ()=>{
return new Promise((resolve,reject) => {
setTimeout(()=>{
resolve(100);
},500);
});
}
let list = ()=>{
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve([1,2,3]);
},500);
});
}
let getList = async ()=>{
let result = await Promise.all([count(),list()]);
return result;
}
//调用
getList().then(result=> {
console.timeEnd('begin'); //begin: 505.557ms后才执行
console.log(result); //[ 100, [ 1, 2, 3 ] ]
}).catch(err=> {
console.timeEnd('begin');
console.log(err);
});
—关于async 函数—
async 函数就是 Generator 函数的语法糖。
对比generator : async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。
const fs=require('fs');
function readFile(url){
return new Promise(function (resolve, reject) {
fs.readFile(url,'utf-8', function (err, data) {
if(err)reject(err);
resolve(data);
})
})
}
function* read(){//generation函数
const f1=yield readFile('../17.promise原理/201808-promise/1.txt');
const f2=yield readFile('../17.promise原理/201808-promise/2.txt');
console.log(f1,'Generator');
console.log(f2,'Generator');
}
//async
async function read2(){
const f1=await readFile('../17.promise原理/201808-promise/1.txt');
const f2=await readFile('../17.promise原理/201808-promise/2.txt');
console.log(f1,'async');
console.log(f2,'async');
}
read2()
async函数对 Generator 函数的改进
- Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器,async函数的执行,与普通函数一模一样。
- 更好的语义
- co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。
- 返回值是 Promise,比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作
async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。
- async 函数有多种使用形式。(async后面必须紧跟着函数)
// 函数声明
async function foo() {}
// 函数表达式
const foo = async function () {};
// 对象的方法
let obj = { async foo() {} };
obj.foo().then(...)
// Class 的方法
class Storage {
constructor() {
this.cachePromise = caches.open('avatars');
}
async getAvatar(name) {
const cache = await this.cachePromise;
return cache.match(`/avatars/${name}.jpg`);
}
}
const storage = new Storage();
storage.getAvatar('jake').then(…);
// 箭头函数
const foo = async () => {};
错误处理机制
- 产生错误的情况:async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态,会阻断后续代码的执行。
async function fn(){
throw new Error('err')//抛出错误,阻断代码执行
console.log('上边抛出错误')
}
fn()
async function fn2(){
await new Promise(function (resolve, reject) {//抛出错误,阻断代码执行
reject('err2');
})
console.log('上边抛出错误')
}
fn2()
- async处理错误有两种方式
//处理错误最常用的方式try catch
async function fn(){
try{
await new Promise(function (resolve, reject) {
reject('err')
})
}catch(err){
console.log('上边抛出错误'+err)
}
}
fn()
//用catch捕捉错误(因为async返回的是一个promise)
//内部捕捉
async function fn2(){
await new Promise(function (resolve, reject) {
reject('err2')
}).catch(function (err) {
console.log('上边抛出错误'+err)
})
console.log('这接着执行')
}
fn2()
//外部捕捉
async function fn3(){
await new Promise(function (resolve, reject) {
reject('err3')
})
console.log('这就不执行了')
}
fn3().catch(function (err) {
console.log('上边抛出错误'+err)
})
await 命令
- 正常情况下,await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。个人认为:只有后面接一个Promise对象或thenable时await才会有意义
async function f() {
// 等同于
// return 123;
return await 123;
}
f().then(v => console.log(v))
// 123 then中的回调都是异步的(来源于promiseA+规范)
- await命令后面是一个thenable对象(即定义then方法的对象),那么await会将其等同于 Promise 对象
//回想一下手写封装的promise原理中的resolvePromsie对象,判断的就是thenable时,递归判断
//任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。
async function f() {
await Promise.reject('出错了');
await Promise.resolve('hello world'); // 不会执行
}
使用注意点
- 前面已经说过,await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try…catch代码块中。
- 多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。
//独立的异步操作(不相互依赖的情况)
// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
//以上两种都是并发的方式,效果一样
- 异步函数之间相互依赖,需要继发执行
// 写法一
async function main() {
var foostr = await getFoo();
var barstr = await getBar();
console.log(foostr )
console.log(barstr);
}
main()
// 写法二
async function main2() {
let docs = [getFoo, getBar];
for (let doc of docs) {
var str = await doc();
console.log(str);
}
}
main2()
//对比并发和继发执行的不同
- await命令只能用在async函数之中,如果用在普通函数,就会报错
- async 函数可以保留运行堆栈。(不太清楚用途)
—实现原理—
async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。
async function fn(args) {
// ...
}
// 等同于
function fn(args) {
//spawn这个自动执行器,执行一个generation
return spawn(function* () {
// ...
});
}
//自动执行器
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); });
});
}
关于执行顺序的问题
async function async1 () {
console.log('async1 start')
await async2();
console.log('async1 end')
}
async function async2 () {
console.log('async2')
}
console.log('script start')
setTimeout(function () {
console.log('setTimeout')
}, 0)
async1();
new Promise (function (resolve) {
console.log('promise1')
resolve();
}).then (function () {
console.log('promise2')
})
console.log('script end')
关于执行顺序的注意事项:
代码执行顺序,从上到下,从左到右,同步>>异步,微任务>>宏任务
then的回调属于微任务
计时器等异步属于宏任务
await下边的代码都是异步执行的
return await 123 等价 return 123 ,下边的代码不执行
promise的状态不改变,then中的回调不执行
—对比Promise,co,async/await的区别—
1.promise
链式调用,避免回调地狱(连环嵌套)
每次then拿取返回值不能跨then拿取数据,只能拿到当前的上一个传下来的
new Promise(...).then(res1=>{}).then(res2=>{}).then(res3=>{这里想要用res1需要一步一步传下来才行})
2.co
需要引用插件,依赖generator3.async/await()—-最好用!!!!
ES7 中更加标准的解决方案(需要babel)。
语法简洁,代码优雅
每次都能拿到异步函数的返回值,避免嵌套