一、初识await与async
初学到async与await时只是停留在表面,明白await是用来等待函数执行的,async是用来声明一个函数为异步函数的,并且只知道只有声明了async的函数,里面才可以用await。
现在想起来,有一点没错,这两个的确是成对出现的。但是申明了async的函数是异步函数?这个是错的,申明了async的函数还是同步函数,因为我们知道,如果是异步函数,那么他的执行顺序肯定在同步函数之后:
async function fun1(){
console.log("我像是异步函数吗?")
}
function fun2(){
console.log("我是同步函数")
}
fun1()
fun2()
console.log("666")
所以申明async与异步函数并没有直接关系,想要知道为什么,继续看下面:
先说一下async的声明语法:
语法就是:
如果没有声明async将会报错:
二、async有什么作用
它的全名是:await asynchronous(等待异步)
1.先来看看例子:
例子1:
async function testAsync() {
return "hello async";
}
const result = testAsync();
console.log(result);
c:var est> node --harmony_async_await .
Promise { 'hello async' }
打印出来发现,申明了等待异步的函数,并不是直接返回一个我们想要的值,而是一个promise对象。原来是promise对象,那么就可以使用then链语法,一直then下去:
例子1.2
async function async1() {
console.log("async1 start");
console.log("async1 end");
}
let b =async1()
console.log(b)
如果async没有返回值,则直接返回一个undefined,且外面是一个promise包着
例子2:
先来看看第一个普通使用promise的例子:
function conductStr(str){
return new Promise((resolve)=>{
resolve(str.substr(5).split('>'))
})
}
//下面代码都是废话,主要看上面绿色区域
let my_str = conductStr('当前位置:初三网 >肇庆中考 > 肇庆地区高中>肇庆市第四中学')
my_str.then((response)=>{
console.log(response)
}).catch(err=>{
console.log(err)
})
再来看看处理同一操作的async可以怎么写:
async function conductStr(str){
return ( str.substr(5).split('>'))
}
//同样下面是废话
let my_str = conductStr('当前位置:初三网 >肇庆中考 > 肇庆地区高中>肇庆市第四中学')
my_str.then((response)=>{
console.log(response)
}).catch(err=>{ console.log(err) })
同样输出结果:
我们可以看到,async申明的函数会把返回结果直接实例化一个new promise,然后通过resolve把参数传出去。
所以总结一下:申明async函数的返回值会默认返回一个promise,这个返回值可以使用then链语法,一直写下去。
带 async 关键字的函数,它使得你的函数的返回值必定是 promise 对象
也就是
如果async关键字函数返回的不是promise,会自动用Promise.resolve()包装
如果async关键字函数显式地返回promise(即你自定义的promise),那就以你返回的promise为准
三、await有什么作用
await是用来等待后面的表达式执行完毕,才能继续执行下去,await后面的表达式可以是一个函数,也可以是一个普通表达式子
看看例子:
(1)异步方法1,2都用了await的情况:(注意,我所说的异步方法是指里面的settimeout,而不是外面的function,后面也是这样,而等待异步方法是指async函数,下面就不在继续提醒了。)
function Async1() {
return new Promise((resolve)=>{
setTimeout(() => {
return resolve("我是异步方法1");
}, 1000);
})
}
function Async2() {
return new Promise((resolve)=>{
setTimeout(() => {
return resolve("我是异步方法二");
}, 10);
})
}
async function test() {
const v1 = await Async1();
const v2 = await Async2();
console.log(v1, v2);
}
test();
可以看到直接输出resolve的参数 。
(2)异步方法1不用await,异步方法2用await
function Async1() {
return new Promise((resolve)=>{
setTimeout(() => {
return resolve("我是异步方法1");
}, 1000);
})
}
function Async2() {
return new Promise((resolve)=>{
setTimeout(() => {
return resolve("我是异步方法二");
}, 10);
})
}
async function test() {
const v1 = Async1();
const v2 = await Async2();
console.log(v1, v2);
}
test();
可以看到没有await,直接返回一个pending,那么是否await来等待一个具体值呢?这个我们等下再考虑。
(3)await是否可以等待其它函数,或者表达式:
function normal(){
let a=1;
return"我是同步函数"
}
async function Async0(){
return"我是异步函数零"
}
function Async1() {
return new Promise((resolve)=>{
setTimeout(() => {
resolve("我是异步方法一")
}, 1000);
})
}
function Async2() {
return new Promise((resolve)=>{
setTimeout(() => {
return resolve("我是异步方法二");
}, 10);
})
}
async function test() {
const num = await (1+1);
const n = await normal()
const v0 = await Async0();
const v1 = await Async1();
const v2 = await Async2();
console.log(n,v0,v1, v2,num);
}
test();
问题答案如上面演示,答案是可以的。
(4)同步方法没有返回值时,await会怎么处理
function normal(){
let a=1;
}
function Async1() {
return new Promise((resolve)=>{
setTimeout(() => {
resolve("我是异步方法一")
}, 1000);
})
}
function Async2() {
return new Promise((resolve)=>{
setTimeout(() => {
return resolve("我是异步方法二");
}, 10);
})
}
async function test() {
const n = await normal()
const v1 = await Async1();
const v2 = await Async2();
console.log(n,v1,v2)
}
当同步没有返回值时,await默认返回undefined,因为await会认为同步函数执行完了,然后await知道同步函数没有返回值。
(5)异步方法没有返回值时,await怎么操作
function normal(){
let a=1;
}
function Async1() {
return new Promise((resolve)=>{
setTimeout(() => {
resolve("我是异步方法一")
}, 1000);
})
}
function Async2() {
return new Promise((resolve)=>{
setTimeout(() => {
// return resolve("我是异步方法二");把异步方法二返回值注释掉
}, 10);
})
}
async function test() {
const n = await normal()
const v1 = await Async1();
const v2 = await Async2();
console.log(n,v1,v2)
}
test();
此时await会直接退出,其实我觉得应该结果会直接阻塞才对,因为异步代码没有返回值,await就会一直等待,await一直等待,test函数就会奔溃,最后退出才对;因为await可以判断同步代码没有返回值,但是异步代码返回值什么时候返回,它是无法知道的。
说明白一点,我们知道通常我们使用promise是用来包着一个异步操作的,等待异步操作完成,才把结果resolve出去,但是这个异步操作什么时候完成,没有人会知道,就像我们在像网络世界发起请求时,如果服务器响应速度快,我们就很快得到数据,相反,要是服务器很慢,我们就很晚才拿到数据,但是具体请求数据要多久才能拿到数据我们是不知道的,可能1ms,可能3s,亦或是1分钟,一天,十天,半个月,一年都有可能,只不过我们的日常应用都做了处理,如果超过一定时间没有请求得到数据,那么浏览器或者网页会返回404,或者其他通知用户请求数据失败的消息,以优化用户体验。
回到上面那个例子,settimeout就是一个异步操作,里面resolve就是包含着我们的数据,如果我们一直不resolve,外面就一直拿不到数据,所以我自己觉得要是await在超出一段时间拿不到数据,它就认为请求失败,整个async函数全部退出,即使最前面的几个await拿到值了,它也会导致程序直接退出。这里说的不太准确,应该说阻塞才退,其实前面的await已经拿到值了,只不过后面的输出函数被阻塞了,输出不了。
(6)声明了async的函数,有用await与不用await的区别:
不用await:
async function async1() { console.log( 'async1 start' ) async2() console.log( 'async1 end' ) } async function async2() { console.log( 'async2' ) } async1() console.log( 'script start' )
执行顺序与普通函数没什么区别:
用了await:
async function async1() { console.log( 'async1 start' ) await async2() console.log( 'async1 end' ) } async function async2() { console.log( 'async2' ) } async1() console.log( 'script start' )
所以对于await来说,等到的对象分2个情况
- 不是promise对象
- 是promise对象
如果不是 promise , await会阻塞async函数内部的代码,先执行async外面的同步代码,同步代码执行完,再回到async内部,把这个非promise的东西,作为 await表达式的结果
如果它等到的是一个 promise 对象,await 也会阻塞async内部后面的代码,先执行async外面的同步代码,等着 Promise 对象 fulfilled,然后把 resolve 的参数作为 await 表达式的运算结果。
有的人可能会疑惑,为什么阻塞了,那个async2还是打印出来了,这其实是代码执行顺序是从由到左执行的,先执行了asyn2(),然后发现有await之后,再阻塞内部,然后跳到外部先执行。
看到这里的同学可以去网上查查js的事件循环,然后查一下之前今日头条的那道面试题,不过我看到那个面试题的答案有两种,有个博主是写的很详细,而且解释起来也令我信服,但是执行结果和他给出的答案的有点出入,我也不知道怎么回事。感兴趣的可以看看:https://www.cnblogs.com/fundebug/p/10095355.html#4837719
四、总结
(1)async用来申明一个函数是等待异步函数,只有申明了async的函数,内部才可以使用await;
(2)申明了async函数,那么返回值会是一个promise,返回的类型语法格式:promise{‘参数’},但是它仍属于同步方法。
(3)await是用来等待一个表达式的,这个表达式可以是任何一个表达式,当返回的为非promise表达式时,无论await等到的是什么,等待异步函数最终会全部执行。当等到的是promise,而且这个promise没有返回值时,它就会导致等待异步函数阻塞,从await开始一直阻塞,然后退出,此时程序会输出await之前的执行结果。(就像promise的中文意思一样,承诺,就是它承诺了会返回一个数据,但是结果它没有返回,如此不诚信的行为将会导致整个函数异常)
(4)当await内部阻塞时,会先去外面把其他同步函数执行完,再返回到当前阻塞代码继续执行。
附上我画的声明了async的函数与promise函数与异步请求函数的关系:
一个async可以包含多个promise,使用await达到多个promise.then()同步的效果:
function fun1(){
return new Promise((resolve)=>{
setTimeout(() => {
resolve('任务1')
}, 500);
})
}
function fun2(){
return new Promise((resolve)=>{
setTimeout(() => {
resolve('任务2')
}, 100);
})
}
async function totalTask(){
await fun1().then((response)=>{
console.log(response)
})
await fun2().then((response)=>{
console.log(response)
})
}
totalTask()
若没有async:
function fun1(){
return new Promise((resolve)=>{
setTimeout(() => {
resolve('任务1')
}, 500);
})
}
function fun2(){
return new Promise((resolve)=>{
setTimeout(() => {
resolve('任务2')
}, 100);
})
}
function totalTask(){
fun1().then((response)=>{
console.log(response)
})
fun2().then((response)=>{
console.log(response)
})
}
totalTask()
五、给出await与async在实际应用中的例子:
1. async/await 的优势在于处理 then 链
单一的 Promise 链并不能发现 async/await 的优势,但是,如果需要处理由多个 Promise 组成的 then 链的时候,优势就能体现出来了(很有意思,Promise 通过 then 链来解决多层回调的问题,现在又用 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();
// c:var est>node --harmony_async_await .
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
// doIt: 1507.251ms
输出结果 result
是 step3()
的参数 700 + 200
= 900
。doIt()
顺序执行了三个步骤,一共用了 300 + 500 + 700 = 1500
毫秒,和 console.time()/console.timeEnd()
计算的结果一致。
如果用 async/await 来实现呢,会是这样:
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 实现是一样的,但是这个代码看起来是不是清晰得多,几乎跟同步代码一样
2. 还有更酷的
现在把业务要求改一下,仍然是三个步骤,但每一个步骤都需要之前每个步骤的结果。
function step1(n) {
console.log(`step1 with ${n}`);
return takeLongTime(n);
}
function step2(m, n) {
console.log(`step2 with ${m} and ${n}`);
return takeLongTime(m + n);
}
function step3(k, m, n) {
console.log(`step3 with ${k}, ${m} and ${n}`);
return takeLongTime(k + m + n);
}
这回先用 async/await 来写:
async function doIt() {
console.time("doIt");
const time1 = 300;
const time2 = await step1(time1);
const time3 = await step2(time1, time2);
const result = await step3(time1, time2, time3);
console.log(`result is ${result}`);
console.timeEnd("doIt");
}
doIt();
// c:var est>node --harmony_async_await .
// step1 with 300
// step2 with 800 = 300 + 500
// step3 with 1800 = 300 + 500 + 1000
// result is 2000
// doIt: 2907.387ms
除了觉得执行时间变长了之外,似乎和之前的示例没啥区别啊!别急,认真想想如果把它写成 Promise 方式实现会是什么样子?
function doIt() {
console.time("doIt");
const time1 = 300;
step1(time1)
.then(time2 => {
return step2(time1, time2)
.then(time3 => [time1, time2, time3]);
})
.then(times => {
const [time1, time2, time3] = times;
return step3(time1, time2, time3);
})
.then(result => {
console.log(`result is ${result}`);
console.timeEnd("doIt");
});
}
doIt();
参考文章:https://segmentfault.com/a/1190000007535316