1、原理1
async函数返回一个 Promise 对象,可以使用then方法添加回调函数。例:
// async返回的是Promise 对象?
async function testAsync() {
return 'hello';
// Promise对象的返回值如果不是Promise,会通过Promise.resolve()转化为Promise,再进行处理
}
const result = testAsync()
console.log(result);//Promise { 'hello' }说明async返回的是Promise对象
那既然async返回的是Promise对象,那么async后面的函数可以接.then()或者.catch()...嘛?我们试一试就知道了。
// async返回的是Promise 对象,并且可以使用Promise的方法?
async function testAsync() {
// await await等待还是Promise对象
return 'hello';
}
testAsync().then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
// hello 说明async返回的是Promise对象,而且可以使用Promise的方法,并且默认状态是resolved
上面代码说明,async函数内部return语句返回的值,会成为then方法回调函数的参数
2、原理2
当async函数内部抛出错误的时候,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被.then()方法的第二个回调函数接收或者.catch()方法回调函数接收到。
// async函数内部抛出错误或者Promise状态为reject
async function testError(){
//throw new Error('出错啦~');
await Promise.reject('出错了'); //await前面有return和没有return效果样
}
testError( )
// .then(()=>{}, (error)=>{ console . log(error);})
.catch(error=> {console.log(error);})
//Error: 出错啦~
3、原理3
await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。代码说明:
// await
async function getName(){
// return '返回值';
return await '返回值';//上面直接return等价于这个return
}
getName().then( result=> {console.log(resu1t);})
//返回值
4、原理4
await的使用,必须要有async,async返回的是一个Promise对象,await等待的就是这个Promise对象,所以await不能没有async(但是async可以没有await)。如果await没有async会报错:
// await没有async会报错
function testAwait(){
return await '西红柿炒鸡蛋'
}
testAwait().catch(error=>{
console.1og(error);
})
//SyntaxError: await is only valid in async function
二、深入Async-await规则
1、async封装Promise
// async封装Promise
async function fn1() {
return '喜洋洋';//相当于return Promise.resolve('喜洋洋')
const data = await fn1();//接收data值
}
fn1()//执行async两数,返回的是一个Promise对象
.then(data => {
console.log('content = ', data)
})
//content = 喜洋洋
2、await相当于then
// await---. then( )
async function getName(){
const operate = Promise.resolve('白雪公主')//执行两数
const name = await operate //await 相当子Promise的then operate. then(name=>{})
console.log('name:' ,name)
}
getName();
(async function(){
const person = await '七个小矮人' //await Promise.resolve('七个小矮人') await 后面不跟Promise,也会被封装成Promise
console.1og('person:',person)//400
})();//自执行函数
//name:白雪公主
//person:七个小矮人
3、多个await时,按时序执行
当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。
async function testOrder() {
await Promise.reject('出错了') //UnhandLedPromiseRejectionWarning:出错了
await Promise. resolve( 'hello world'); //不会执行
}
testorder();
4、try…catch相当于catch
如果希望即使前一个异步操作失败,也不要中断后面的异步操作。可将第一个await放在try...catch结构里面,这样不管这个异步操作是否成功,第二个await都会执行。
// try...catch
!(async function () {
const testError = Promise.reject( '出错啦~~' )//rejected状态
// const testError=throw new Error( '出错啦www ');
try {
const result = await testError; //await 相当F then,但是reject 不会触发then
console.1og('success: '+result) // 不会输出,因为const result = await testError 被报错,被catch捕获
} catch (error) {
console.error('error: '+error)//try...catch相当F Promise的catch
}
})()
//error:出错啦~~
当await后面是Promise对象的时候,我们也可直接在await后面直接.catch捕获错误:
async function testError() {
await Promise.reject('出错了')
.catch(error => console.log(error));//这里捕获错误, 不会影响下一个await执行
return await Promise.resolve('hello world');
}
testError()
.then(result => console.log(resu1t))
三、解析Async-await语法
我们浅浅看一个面试题:
//面试题
function getJSON() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.1og(2);
resolve(2)
}, 2000 )
})
}
async function testAsync() {
await getJSON()
console.1og(3);
}
testAsync()
//2
//3
我们必须知道:
(1)await不能单独出现,其函数前面一定要有async。
(2)await会干两件事:
第一,将写在await后面的代码放到async创建的那个Promise里面执行。
第二、将写在await下面的代码放到前一个创建的那个Promise对象的.then里面执行。
(3)await返回的也是Promise对象,他只是把await下面的代码放到了await返回的promise的.then里面执行。
翻译如下:
function getJSON() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.1og(2);
resolve(2)
}, 2000)
})
}
// 编译成 Promise 原理
function testAsync() {
return Promise.resolve().then(() => {
return getJSON();
})
.then(() => {
console.1og(3);
})
}
testAsync()
四、拓展Async-await应用
1、场景1
// Promise解决方式
function doCallback(n) {
var myPromise = new Promise(function (resolve, reject) {
//处理异步任务
var flag = true;
setTimeout(function () {
if (flag) {
resolve(n)
} else {
reject('失败')
},0)
})
return myPromise;
}
doCallback(1)
.then((result) => { //then足成功执行的方法返回的还是一个Promise对象
console.log(result);//打印张三res 是执行
return fn(2);
})
.then((result) => {
console.log(result);
return fn(3)
})
.then((result) => {
console.log(resu1t);
return fn(4)
})
.then((result) => {
console.log(result);
})
.catch((result) => { // catch是失败执行的方法
console.log(result);
})
// 好多then形成.then链
通过以上Promise方法,可以明显解决回调地狱“向右移”的浮夸表现,但是,Promise是基于 then, catch 的链式调用,但也是基于回调函数。.then链多多少少还是违背原生代码,async-await更加贴近于原生代码:
// 封装一个返回promise的异步任务
function doCallback(str) {
var myPromise = new Promise(function (resolve, reject) {
//处理异步任务
var flag = true;
setTimeout(function () {
if (flag) {
resolve(str)
} else {
reject('失败')
},0)
})
return myPromise;
}
// 封装 个执行上述异 步任务的async 函数
async function testAsync() {
var result1 = await doCallback(1); //await直接拿到fn()返回的promise的数据,并且赋值给result
var result2 = await doCallback(2); //await后面的代码,都可以看做是异步回调callback里的内容, 都是异步的
var result3 = await doCallback(3);
var result4 = await doCallback(4);
console.log(result1);
console.log(resu1t2);
console.log(resu1t3);
console.log(resu1t4);
}
//这样是不是简洁优雅多了呢?
//执行两数
testAsync();
//1
//2
//3
//4
async / await 和 Promise 并不互斥,二者相辅相成。同时async / await 并不能改变异步的本质,js是单线程的,异步需要回调,都是要基于 event loop 来实现
2、场景2
function wait() {
return new Promise(resolve =>
setTimeout(resolve, 1000)
)
}
async function main() {
console.time();
const x = wait();
const y = wait();
const z = wait();
await x;
await y;
await z;
console.timeEnd();
}
main(); // default: 1.002s
async function main2() {
console.time();
await wait();
await wait();
await wait();
console.timeEnd();
}
main2(); // default: 3.005s
main的运行时间是1s多一点,这是因为:a,b,c的异步请求会按顺序发起。而这个过程是不需要互相依赖等待的。等到wait的时候,其实是比较那个异步耗时最多。就会等待最长。最长的耗时就是整体的耗时。
题目中的setTimeout()就是个异步任务。在所有同步任务执行完之前,任何的异步任务是不会执行的。
new Promise(xx)相当于同步任务, 会立即执行。
所以: x,y,z 三个任务是几乎同时开始的, 最后的时间依然是1000ms (比这稍微大一点点, 超出部分在1x1000ms之内)。
3、场景3 - forEach 中用 await 会产生什么问题?怎么解决这个问题?
问题:对于异步代码,forEach 并不能保证按顺序执行。
举个例子:
async function test() {
let arr = [4, 2, 1]
arr.forEach(async item => {
const res = await handle(item)
console.log(res)
})
console.log('结束')
}
function handle(x) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(x)
}, 1000 * x)
})
}
test()
我们期望的结果是:
4 2 1 结束
但是实际上会输出:
结束 1 2 4
问题原因
这是为什么呢?我想我们有必要看看forEach底层怎么实现的。
// 核心逻辑
for (var i = 0; i < length; i++) {
if (i in array) {
var element = array[i];
callback(element, i, array);
}
}
可以看到,forEach 拿过来直接执行了,这就导致它无法保证异步任务的执行顺序。比如后面的任务用时短,那么就有可能抢在前面的任务之前执行。
解决方案
如何来解决这个问题呢?
其实也很简单, 我们利用for...of就能轻松解决。
async function test() {
let arr = [4, 2, 1]
for(const item of arr) {
const res = await handle(item)
console.log(res)
}
console.log('结束')
}
解决原理——Iterator
这个问题看起来好像很简单就能搞定,那么想过这么做为什么可以成功吗?
其实,for...of并不像forEach那么简单粗暴的方式去遍历执行,而是采用一种特别的手段——迭代器去遍历。
首先,对于数组来讲,它是一种可迭代数据类型。那什么是可迭代数据类型呢?
原生具有[Symbol.iterator]属性数据类型为可迭代数据类型。如数组、类数组(如arguments、NodeList)、Set和Map。
可迭代对象可以通过迭代器进行遍历。
let arr = [4, 2, 1];
// 这就是迭代器
let iterator = arr[Symbol.iterator]();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
// {value: 4, done: false}
// {value: 2, done: false}
// {value: 1, done: false}
// {value: undefined, done: true}
因此,我们的代码可以这样来组织:
async function test() {
let arr = [4, 2, 1]
let iterator = arr[Symbol.iterator]();
let res = iterator.next();
while(!res.done) {
let value = res.value;
console.log(value);
await handle(value);
res = iterater.next();
}
console.log('结束')
}
// 4 // 2 // 1 // 结束
多个任务成功地按顺序执行!其实刚刚的for...of循环代码就是这段代码的语法糖。
重新认识生成器
回头再看看用iterator遍历[4,2,1]这个数组的代码。
let arr = [4, 2, 1];
// 迭代器
let iterator = arr[Symbol.iterator]();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
// {value: 4, done: false}
// {value: 2, done: false}
// {value: 1, done: false}
// {value: undefined, done: true}
咦?返回值有value和done属性,生成器也可以调用 next,返回的也是这样的数据结构,这么巧?
没错,生成器本身就是一个迭代器。
既然属于迭代器,那它就可以用for...of遍历了吧?
当然没错,不信来写一个简单的斐波那契数列(50以内):
function* fibonacci(){
let [prev, cur] = [0, 1];
console.log(cur);
while(true) {
[prev, cur] = [cur, prev + cur];
yield cur;
}
}
for(let item of fibonacci()) {
if(item > 50) break;
console.log(item);
}
// 1 // 1 // 2 // 3 // 5 // 8 // 13 // 21 // 34
这就是迭代器的魅力:)同时又对生成器有了更深入的理解,没想到Generator还有这样的身份。
总结
async-await是promise的语法糖,不仅让我们书写代码时更加流畅,而且增强了代码的可读性。特别注意的是:虽然async-await 是建立在 Promise机制之上的,但是并不能取代其地位,他们两者相辅相成,息息相关。