回调函数
发布订阅模式
// 事件总线 | 发布订阅
class EventEmitter {
constructor() {
this.cache = {}
}
on(name, fn){
if(this.cache[name]){
this.cache[name].push(fn)
}else{
this.cache[name] = [fn]
}
}
off(name, fn){
const tasks = this.cache[name]
if(tasks){
const index = tasks.findIndex((f) => f === fn || f.callback === fn)
if(index >= 0){
tasks.splice(index, 1)
}
}
}
emit(name, once = false){
// 创建副本,如果回调函数内解析注册相同事件,会造成四循环
const tasks = this.cache[name].slice()
for(let fn of tasks){
fn()
}
if(once){
delete this.cache[name]
}
}
}
// 测试
const eventEmitter = new EventEmitter()
eventEmitter.on("beforeRun", function(){
console.log("注册1")
})
eventEmitter.on("beforeRun", function(){
console.log("注册2")
})
eventEmitter.emit("beforeRun")
Promise
一些特性
- Promise是一个类,实例化new这个类的时候会传入一个执行器函数executor,这个执行器会立即执行;
- Promise会有三种状态
- pending 进行中
- fulfilled 完成
- rejected 失败
- 状态只能由pending -> fulfilled 或者pending -> rejected,且一旦发生改变便不可二次修改;
- executor执行成功,调用resolve方法,执行Promise.prototype.then传递的回调函数
- executor执行失败,调用reject方法,执行Promise.prototype.catch传递的回调函数,或者Promise.prototype.then传递的第二个函数参数
- Promise支持异步、链式调用
Promise的出现是为了解决“回调地狱”问题
Promise的缺点
- Promise的内部错误使用try catch捕获不到,只能使用then的第二个回调或catch捕获
- Promise一旦新建就会立即执行,无法取消
queueMicrotask实现微任务
很多手写版本都是是有setTimeOut去做异步处理,但是setTimeOut属于宏任务,这与Promise是一个微任务相矛盾,所以要选择一种创建微任务的方式去实现。
这里我们有几种选择,一种就是Promise A+规范中提到的,Promise.nextTick(Node端)与MutationObserver(浏览器端),考虑到利用这两种方式需要做环境判断,所以在推荐使用queueMicrotask创建微任务。
生成器generator
底层实现机制——协程(Coroutine)
V8实现函数的暂停和恢复——协程
协程
- 协程是一种比线程更加轻量级的存在
- 你可以把协程看成是跑在线程上的任务
- 一个线程上可以存在多个协程,但是一个线程上同时只能执行一个协程
- 线程的执行是在内核态,由操作系统来控制
- 协程的执行是在用户态,是完全有程序来进行控制
- 协程切换是在用户态执行,而线程切换时需要从用户态切换到内核态,在内核态进行调度
- 协程相对于线程来说更加轻量、高效
一些特性
- Generator函数是一个生成器,执行它会返回一个迭代器;
- 这个迭代器同时也是一个协程
- 通过调用生成器的next()方法可以让该协程执行
- 通过yield关键字可以让该协程暂停,交出主线程控制权
- 通过return关键字可以让该协程结束
async await
async function实现原理
- async function是通过Promise + Generator来实现的
- generator是通过协程来控制程序调度的
- 在协程中执行异步任务时,先用promise封装该异步任务,如果异步任务完成,会将其结果放入微任务队列中,然后通过yield让出主线程执行权,继续执行主线程js,主线程js执行完毕后,会去扫描微任务队列,如果有任务则取出任务进行执行,这时通过调用迭代器的next(result)方法,并传入任务执行结果result,将主线程执行权转交给该协程继续执行,并且将result赋值给yield表达式左边的变量,从而以同步的方式实现了异步编程
- 所以说到底async function还是通过协程 + 微任务+ 浏览器事件循环机制来实现的
分析一道题,理解async的实现原理
(1)new Promise传入的函数,输出promise start
(2)执行resolve方法,该方法内部异步调用第一个then传入的函数,
即then传入的函数放入微任务队列
(3)执行asyncFun1() ,创建协程asyncFun1
(4)new Promise传入函数asyncFun1
(5)执行asyncFun1方法,输出async start
(6)new Promise传入函数asyncFun2
function asyncFun1(){
new Promise((reslove, reject)=>{
var result = asyncFun2()
resolve(result)
}).then(result => {
return result
})
}
(7)执行asyncFun2方法,输出async await1
(8)asyncFun2执行完成,返回结果为undefined,
asyncFun2 Promise的resolve执行回调函数的操作放入微任务队列,
asyncFun2 Promise的回调函数应该是浏览器内部实现的,猜测类似如下
then(value=>{
return value //value就是asyncFun2的执行结果
})
然后通过yield让出主线程执行权
(9)继续执行主线程js,主线程js执行完毕,扫描微任务队列
(10)发现微任务队列中有第一个任务,即promise的第一个then传入的回调函数,
则执行该函数,输出promise then1
promise第一个then也是一个promise,简称promise then1
promise then1将promise第二个then传入的函数作为回调函数放入微任务
(11) 发现微任务队列中的第二个任务,即await asyncFun2的回调函数
(12)执行await asyncFun2的回调函数,将结果传回给协程asyncFun1,
并恢复协程asyncFun1的执行
(13) 运行await asyncFun3()这行代码
(14) new Promise传入asyncFun3
(15) 执行asyncFun3方法,输出async await2
asyncFun3 Promise的resolve执行回调函数的操作放入微任务
然后让出主线程执行权
(16)继续执行主线程js,主线程js执行完毕,扫描微任务队列,
发现第一个任务promise then1的回调函数,则执行,输入promise then2,
发现第二个任务async await2
| 标题 | |
|---|---|
async function asyncFun1(){
console.log('async start');
await asyncFun2()
await asyncFun3()
console.log('async end')
}
async function asyncFun2(){
console.log('async await1');
}
async function asyncFun3(){
console.log('async await2')
}
new Promise((reslove)=>{
console.log('promise start')
reslove()
}).then(()=>{
console.log('promise then1')
}).then(()=>{
console.log('promise then2')
}).then(()=>{
console.log('promise then3')
})
asyncFun1()
输出结果为:
promise start
async start
async await1
promise then1
async await2
promise then2
async end
promise then3
根据上面的输出结果,发现async函数里面从第二个await开始,就和promise.then交替输出,为什么是交替输出呢?
async/await底层实现
async/await技术背后的密码就是Promise和生成器的应用,往底层说就是微任务和协程应用。
async function foo() {
console.log(1) //第2个输出
let a = await 100
console.log(a)
console.log(2)
}
console.log(0) // 第1个输出
foo() // 启动foo协程
console.log(3)
当执行到 await 100 时,会默认创建一个Promise对象。代码如下所示
let p = new Promise((resolve, reject) => {
resolve(100)
})