刚开始学 nodejs 的时候,被它的异步搞蒙了。并不像我们写 C 或者 Java 一样将前一步执行完才进行下一步。
举个例子:
1 2 3 4 5 6 7 8
| console.log('begin')
setTimeout(() => { console.log('1s') }, 1000)
console.log('end')
|
不管运行多少次,结果都是一样的。
我预期的结果是下面这样的
那代码就要这么写
1 2 3 4 5 6 7
| console.log('begin')
setTimeout(() => { console.log('1s') console.log('end') }, 1000)
|
为什么要这么写呢?因为setTimeout
的第一个参数是一个异步回调函数。
等等,异步?难道还有同步的回调函数吗?
有,我们看下异步和同步的两种回调函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| const fs = require('fs')
let result1 = 'result1' let result2 = 'result1'
function readFile() { fs.readFile('./msyql.js', () => { result1 = 'readFile' }) return result1 }
function sync() { result2 = 'change' return result2 }
function func(cb) { return cb() }
func(readFile) func(sync) console.log(result1) console.log(result2)
|
可能已经发现一个问题了。同步回调在这里好像并没有正在存在的必要,直接在调用前一个同步方法后面调用下一个同步方法就可以了。同步回调有助于我们更好的编写和阅读代码,这里先不提。那么剩下的是异步回调函数的作用。
异步回调函数是干嘛用的
异步回调函数是 nodejs 现实异步的方式,那么只有这种方式吗?当然不是,可以用events
实现发布订阅的模式实现异步。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const EventEmitter = new require('events').EventEmitter const events = new EventEmitter() console.log('begin') events.on('done', (data) => { console.log(data) }) setTimeout(() => { console.log('1s') events.emit('done', 'timeout') console.log('end') }, 1000)
|
但是 events
的形式,可能会因为 event name
过多,导致编码困难,并没有回调函数的方式灵活。
但是回调函数也有缺点,就是嵌套严重,也就是我们常说的回调地狱(不知道回调地狱的,自行搜索)。
有没有更优雅的方式使用 JavaScript 的异步呢?像写同步代码一样简单的方式?答案是有的。
Promise + async await
Promise 是为了解决回调地狱的一个解决方案。把原来的回调嵌套的方式使用链式的方式来执行。视觉上比回调函数好那么一点,但是本质其实还是回调函数,而且链式调用里面也是存在回调函数的。
我们把 demo2 改一下,使用 Promise 写,结果还是和 demo2 一样的。
1 2 3 4 5 6 7 8 9 10 11 12
| console.log('begin')
new Promise((resolve) => { setTimeout(() => { resolve('1s') }, 1000) }).then((res) => { console.log(res) }).then(() => { console.log('end') })
|
当你把最后的 then 里面的 log 写到 Promise 的下面的时候,发现end
会比1s
先打印出来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| console.log('begin')
new Promise((resolve) => { setTimeout(() => { resolve('1s') }, 1000) }).then((res) => { console.log(res) }) console.log('end')
|
所以还是和异步回调一样的,只是把 callback 写成 then 罢了。那有没有更好的方法呢?
有,async await
!
async await
其实是 Promise 的语法糖。语法糖,就是用更优雅的写法来写之前看起来相对比较丑的语法的方式。就像药丸很苦,我们医生为了骗小孩吃下去,就在药丸外边涂上一层糖衣,小孩子以为是糖果就会吃药。
我们简单举个语法糖的例子,箭头函数与函数表达式。这两种方式除了this
,基本上一致的。
1 2 3 4 5 6 7
| const def = function () { console.log('default') }
const arrow = () => { console.log('arrow') }
|
我们用async await
来改写一下 demo5
1 2 3 4 5 6 7 8 9 10 11 12 13
| console.log('begin')
const run = async()=>{ const res = await new Promise((resolve) => { setTimeout(() => { resolve('1s') }, 1000) }) console.log(res) console.log('end') } run()
|
这样看起来就顺眼多了。当然有个要注意的地方,await
必须写在async
修饰的方法里面。