async & await 再次学习.
我从简述某博主的一片博客中看到了一到关于 async/await
的面试题.
代码如下:
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');
问题就是:请正确的说出打印顺序(在浏览器环境中)?
然后,我就将这段代码,帖到了浏览器中,运行了一下.
打印结果是:
// 浏览器环境执行顺序
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
当然我是一脸懵逼的.
完全不清楚,打印结果为什么会是这个样子.
我仅仅只是知道 async/await
是 ES7
推出来的语法.
一般搭配 Promise
来使用.
可以让我们从同步代码的方式,来书写异步代码.
比如下面这个例子.
场景:我需要按顺序的将1.txt和2.txt的内容读取出来,然后拼接成一个字符串.用async/await & promise来做.
1.txt
百度首页的地址是:
2.txt
我希望打印结果是:
百度首页的地址是:https://www.baidu.com
而不是:
https://www.baidu.com百度首页的地址是:
所以,读取两个文件的先后顺序就成了关键.
我们可以利用 async/await & Promise
来以同步的方式书写异步代码.
让代码先读 1.txt,然后在 2.txt
const fs = require('fs')
const path = require('path')
const FILE_ENCODING = 'utf-8'
const readFilePromiseCreate = (filePath) => {
return new Promise((reslove,reject) => {
fs.readFile(filePath, FILE_ENCODING, (err, data) => {
err ? reject(err) : reslove(data)
})
})
}
const
file1Path = path.join(__dirname, '1.txt'),
file2Path = path.join(__dirname, '2.txt')
const readFileWithAsync = async () => {
const txtFromFile1 = await readFilePromiseCreate(file1Path)
const txtFromFile2 = await readFilePromiseCreate(file2Path)
const result = (txtFromFile1 + txtFromFile2).replace('\n','')
console.log(result)
}
readFileWithAsync()
输出结果:
百度首页的地址是:https://www.baidu.com
我之所以能够比较顺快的写出这些代码.完全是基于我对 async/await
& Promise
的几个简单的了解.
-
await
后面必须跟一个Promise
. 且它会等待Promise
执行完毕一直到reslove
或者reject
. 否则它下面的代码不会执行. - 之所以写
async
是因为我知道在语法层面上,await
只能在定义为async
的函数内部使用.
仅此而已.
直到我看到上文章开头的那道面试题,才知道自己对 async/await
的理解远远不够.
于是,就看是了新一轮的研究.
async 关键字起了什么作用?
我们都很清楚,函数是可以设置返回值的.
async
函数,当然也可以使用 return
来设置返回值.
const getSomething = () => {
return 'getSomething'
}
const asyncGetSomething = async () => {
return 'asyncGetSomething'
}
console.log(getSomething())
console.log(asyncGetSomething())
查看打印结果:
getSomething
Promise { 'asyncGetSomething' }
发现,在我们定义为 async
的函数内部,如果使用 return
返回了某个值.
此函数会把这个值使用 Promise.reslove('asyncGetSomething')
包装起来,而不是简单返回.
所以结论1:使用async修饰的函数的返回值,会被 Promise.reslove()包装起来.
假如我们直接拿 async
修饰符函数的 return
返回值,拿到的将是一个 Promise
.
而不是真正在函数内部 return
出来的值.
所以,对于 async
函数的返回值,我们必须使用 .then
来获取.
// 普通函数直接获取返回值
const value = getSomething()
// Promise 返回值,使用.then获取
let value2
asyncGetSomething()
.then((res => {
value2 = res
console.log(value2)
}))
console.log(value)
结果:
getSomething
asyncGetSomething
结论2:async函数返回的数据,同步方式是无法拿到了,必须借助.then来获取.
补充一下:
关于 async 函数没有返回值
当没有返回值时,返回的也不是简单的
undefined
仍然也是使用Promise.reslove(undefined)
console.log((async () => { })())
Promise { undefined }
关于两种函数的类型问题.
console.log(async () => { })
console.log(() => { })
[AsyncFunction]
[Function]
扩展,比如,我一个方法需要传递一个 async
的函数才行.
async function test (fn) {
if (!Object.prototype.toString.call(fn).includes('AsyncFunction')) {
console.log('需要传递一个 async 的函数!!!')
return
}
let data = await fn()
console.log(data)
}
test(() => { })
test(async () => {
return 'await取出来'
})
结果
需要传递一个 async 的函数!!!
await取出来
await 到底在等什么?
await 应该是单词 async wait
词组的缩写.
我之前一直认为, await
后面只能接 Promise
..
但看了一些博客文章,告诉我,其实 await
是一个计算符.
它等待后面表达式的计算结果.
await 后面接 Promise 我已经知道了.
它会等待 Promise 执行完毕(reslove | reject) .
才会执行下面的代码.
那如果我在 await 后面写的一个除了Promise之外的任意表达式呢?
async function test () {
let data = await 1 + 1
console.log(data)
let data2 = await 'hello'
console.log(data2)
let data3 = await (() => 'world')()
console.log(data3)
}
test()
结果:
2
hello
world
所以, await
后面是可以接除了 Promise
之外的其他任意数据的.
但是区别在哪呢?
await 后面接 Promise 和 其他数据的区别.
Promise
和其他的数据类型到底有没有区别?
一般后面接Promise
,即使在内部立即 reslove
出来,它仍然是一个异步的操作.
那如果,await
后面接的是同步数据呢?
那我们来利用 await
会阻塞这种做法来证明一下.
async function test () {
let data = await '同步数据'
console.log(data)
}
console.log('start')
test()
console.log('end')
那么这里就可能有两种情况了.
- 如果即使
await
后面跟的表达式是同步代码
,仍然会进行异步操作的话.返回的结果应该是start end 同步数据
- 如果
await
后面跟的表达式是同步代码
,它就会按照同步代码执行的逻辑执行.输出的结构应该是start 同步数据 end
运行结果:
start
end
同步数据
符合第一种情况.
即使 await
后面跟的不是一个常规的 Promise
. 而是一个普通的表达式.它仍然会把计算表达式的过程丢给 事件循环
,然后利用回调的机制去触发.
所以结论:
-
await
只能在被标记为async
的函数内部使用. -
await
后面一般接Promise
,然后立即执行Promise
.并将Promise
后续的reslove|reject
丢给 EventLoop ,并阻塞下列代码执行. -
await
后面也可以接普通的表达式.代码也不会像同步函数那样执行.仍然会有一些接了Promise
的痕迹.
那道面试题
-
async
默认返回一个Promise
. -
await
后面接Promise
或者 普通表达式,在执行效果上是一致的.
有了上述那些结论,我们在看看看那道面试题.
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');
- 首先定义了两个
async
函数async1
和async2
. 但这里仅仅只是定义,没有调用.所以继续往下看. - 接着调用了
console.log('script start')
. ====>script start
- 后面跟了一个
setTimeout
的函数,那这个函数肯定是丢在EventLoop-1
中了.(毕竟同步代码还没走完) - 调用了
async1()
函数. ====>async1 start
- 在
async1
函数内部第二行调用await async2()
. -
async2
是一个async
函数,所以返回的肯定是一个Promise
.最差也是Promise.reslove(undefined)
.所以这一步的操作丢在了EventLoop-2
,但其内部的console.log('async2')
属于同步代码,所以被调用 ====>async2
-
await
后面的操作就是一个异步的.同时await
会阻塞下面的console.log('async1 end')
- 继续往下走,碰到了一个
new Promise(xxxx)
. -
new Promise()
虽然是个Promise
.但执行异步操作的这一步是同步的.====>promise1
- 然后第二行代码,
resolve();
虽然立即reslove()
但由于是异步,仍然丢在了EventLoop-3
中. - 接着就是最后一行代码
console.log( 'script end');
====>script end
- 到目前为止.所有同步的代码走完了,
EventLoop
还有三个等待执行回调的setTimeout,async2(),以及 new Promise().then()
- 然后由于
async2
是先于new Promise().then
丢在EventLoop
中去的.且它俩基本就是立马执行的reslove
.所以不会有时间差异.谁先进去的谁先被执行. ====>async1 end
- 接着
new Promise().then
====>promise2
- 最后执行
setTimeout
的回调函数 ====>setTimeout
所以推理出来的最终输出结果是:
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
拿出来和一开始的在浏览器中运行的结果进行对比.
// 浏览器环境执行顺序
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
发现结果是一致的.
总结:
-
async
描述了次方法的返回值一定是一个Promise
.而且是使用Promise.reslove(data)
封装的. -
await
后面不管是接一个常规的Promise
还是一个普通的达表示,其执行形式都会和接了Promise
一致.不会因为后面接的是常规表达式,而表现出同步代码的特征. -
await
后面如果接的是一个普通表达式,则计算表达式的结果. -
await
后面如果接的是一个Promise
,则会等待Promise
执行reslove(data)
.返回值是data
. - 如果后面的
Promise
有失败的情况,可以使用.catch
来捕获,否则会跑出异常.且await
接受到的数据是undefined
async function aa () {
let data = await new Promise((resolve, reject) => {
reject('发生错了')
}).catch(err => console.log(err)) || '这是默认值'
console.log(`data:${data}`)
}
aa()
发生错了
data:这是默认值