你可能知道,Javascript语言的执行环境是”单线程”(single thread)。
所谓”单线程”,就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。
这种模式的好处是实现起来比较简单,执行环境相对单纯;坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。
为了解决这个问题,Javascript 语言将任务的执行模式分成两种:同步(Synchronous)和异步(Asynchronous)。
本文列举 6 种异步编程的方法
- 回调事件
 - 事件监听
 - 发布/订阅
 - Promise
 - Generate
 - async/await
一、回调函数
这是异步编程最基本的方法。 ```javascript // setTimeout(callback, delay) 这个方法允许我们传入一个回调函数 const callback = () => console.log(‘I am callback’) 
setTimeout(callback)
```javascript/*** 函数 asyncToSomething 接收 callback 回调函数* 在适当的时候,执行该回调事件* @param {Function} callback*/const asyncToSomething = (callback) => {// 用 setTimeout 代指异步事件setTimeout(() => {const data = {}// do something// ...callback && callback(data)}, 1000)console.log('asyncToSomething')}// 作为回调函数const todo = (data) => {console.log(data)}console.log(1)asyncToSomething(todo)console.log(2)console.log(3)
打印顺序:1 ——> asyncToSomething ——> 2 ——> 3 ——> {}
:::info
1 打印完,asyncToSomething 函数是同步执行的,因此打印了 asyncToSomething
todo 函数虽然需要执行,但它在 setTimeout 中,不会阻塞 JS 继续执行。
于是打印 2、3,最后再打印 {}
这个 todo 函数作为回调函数,在 asyncToSomething 函数中的异步事件 setTimeout 执行过程中执行。 ::: 回调函数的缺点:回调函数层层嵌套,变成“ 回调地狱 ”,不雅观,逻辑复杂时不容易维护。
setTimeout(() => {setTimeout(() => {setTimeout(() => {setTimeout(() => {setTimeout(() => {setTimeout(() => {})})})})})}, 1000)
二、事件监听
另外一种异步编程的思想是采用事件驱动模式。当某个事件发生时,监听该事件的函数就会执行。
const body = document.bodyconst clickHandler = () => console.log('click')// 监听了 body 的 click 事件。// 当触发 click 时,执行 clickHandlerbody.addEventListener('click', clickHandler)
/*** 装饰器:为 object 添加事件监听机制* @param {any} object*/function addListen(object) {const eventHandler = {}const proto = Object.getPrototypeOf(object)const addEventListener = function (eventType, callback) {if (eventHandler[eventType]) eventHandler[eventType].push(callback)else eventHandler[eventType] = [callback]}const removeEventListener = function (eventType, callback) {const index = eventHandler[eventType].indexOf(callback)if (index !== -1) eventHandler[eventType].splice(index, 1)}const trigger = function (eventType, ...args) {eventHandler[eventType].forEach((callback) => callback(...args))}Object.setPrototypeOf(object, {...proto,eventHandler,addEventListener,removeEventListener,trigger})return object}const data = Object.create(null)addListen(data)data.addEventListener('change', function () {console.log(111)})data.trigger('change')
三、发布/订阅
这种方法的性质与”事件监听”类似。而发布/订阅模式,可以让我们通过查看“ 消息中心 ”,了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行。
class MessageCenter {constructor() {this.center = {}}// 注册事件subscribe(type, callback) {if (!this.center[type]) this.center[type] = [callback]else this.center[type].push(callback)}// 注销事件unSubscribe(type, callback) {const index = this.center[type].indexOf(callback)if (index !== -1) this.center[type].splice(index, 1)}// 发布消息public(type, ...args) {if (this.center[type]) this.center[type].forEach(callback => callback(...args))}}// 测试用例const messageCenter = new MessageCenter()function changeHandler(...args) {console.log(...args)}messageCenter.subscribe('change', changeHandler)function asyncTodoSomething() {setTimeout(() => {messageCenter.public('change', 456)}, 500)}asyncTodoSomething()
四、Promise
Promise 对象是 CommonJS 工作组提出的一种规范,目的是为异步编程提供统一接口。
它的思想是,每一个异步任务返回一个 Promise 对象,该对象有一个 then 方法,允许指定回调函数。比如,f1 的回调函数 f2,可以写成:
f1().then(f2)
