你可能知道,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.body
const clickHandler = () => console.log('click')
// 监听了 body 的 click 事件。
// 当触发 click 时,执行 clickHandler
body.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)