# 温习异步
我们知道 Javascript 语言的执行环境是 单线程。也就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务。
在开始之前请大家思考下,我们在前面的环节中大量使用了 async/await,那么它究竟是什么?
# 什么是异步?
异步指的是每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。这种模式虽然实现起来比较简单,执行环境相对单纯,但是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段 Javascript 代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。
# 回调实现异步
小明在看电视,但他肚子饿了。同步的做法是先吃饭后看电视。异步的做法是一边吃饭一边看电视。实现异步的本质是回调,我们使用 nodejs 的 fs 这个 API 接口来看一下异步与同步的区别:
const fs = require('fs')
// 读取文件
let txt1 = fs.readFileSync('./key.txt')
console.log(txt1.toString(), '1')
let txt2 = fs.readFile('./key.txt', (err, txt3) => {
console.log(txt3.toString(), '2')
})
console.log(txt2, '3')
运行结果如下, readFileSync 是一个同步方法,所以当我们打印 txt1 的时候立刻有返回值。而当我们打印 txt2 的时候可以看到返回值是一个 undefined,这说明没有返回值,而数据在 txt3 的回调里打印出来的。

在 NodeJs 中这种 API 太多了,异步是不能直接返回值的,当我们的项目大时又或者随着项目的不断迭代,久而久之嵌套就会越来越多,这个时候代码是你不愿意去维护也没法维护的,这还牵扯出来一个比较好听的名字,叫回调地狱,我们后面再讲该如何优化。
# 异步函数
我们来动手创建一个异步函数,在这里我们来通过 setTimeout 来模拟异步任务, setTimeout 也是回调,这种回调错误不好处理,默认第一个参数为错误,如果有第一个参数,则说明发生了错误,那么我们就要进行相应的处理。
function asynchronous(text, fn) {
setTimeout(() => {
fn(null, '-' + text + '-')
}, 3000)
}
asynchronous('hello nodejs', (err, text) => {
if(err) console.log(err)
console.log(text)
})
// 3s hello nodejs
# 事件实现异步
事件这个词汇相信大家都不陌生,在学习编程的道路上大家或多或少都接触过前端。在浏览器和页面交互时有各种事件,我们用的比较多的就是点击事件,比如当用户点击某个 HTML 元素时执行一段代码。而在 NodeJs 中,也有一个 events 的事件模块,事件的本质就是发布订阅设计模式。之所以能异步,还是因为回调。
class Evente {
private map: any = {}
// 监听器
public on(name: string, fn: Function) {
if (this.map[name]) {
this.map[name].push(fn)
return this
}
this.map[name] = [fn]
return this
}
// 发射器
public emit(name: string, ...args: any[]) {
if (this.map[name]) {
this.map[name].forEach((fn: Function) => {
fn(...args)
})
}
return this
}
}
我们以 name 作为 key 放到 map 里,也就是事件名称。当触发 emit 方法的时候,获得对应的回调进行调用:
