异步编程
消息队列
异步编程中的回调函数是一个很重要的概念,然而,通过回调函数会毁掉我们的代码,导致回调地狱的出现。 JavaScript 的开发人员向该语言添加了 promise 和 async await。
JavaScript 中的异步实现是基于事件循环的。JavaScript 的事件循环模型中有一个消息队列,JavaScript 不断的从消息队列中取出等待的消息,进行消费。
有些方法可以改变消息队列,比如 setTimeout。setTimeout 的第二个参数可以设置为 0,设置为 0 并不意味着马上执行,而是要等到消息队列前面的消息处理完,才能轮到有 setTimeout 放入的消息。所以,setTimeout 的计时器并不准确。大部分网页中的倒计时程序都是由 setTimeout 进行实现的,在秒杀倒计时的场景中,秒杀开始时间为 8点,当到8点的时候,有可能我们还不能点击秒杀按钮。
注意点:web worker、跨域 iframe 有自己独立的栈、堆和消息队列。相当于两个不同的运行时。如果他们要通信,需要通过 postMessage。
这里需要思考一个问题,何时从刚消息队列中取数据消费呢?
setTimeout 和 Promise 的区别
通过 debugger 进行调试。设计用例。
可参考链接:
- https://www.youtube.com/watch?v=8aGhZQkoFbQ
- https://www.youtube.com/watch?v=cCOL7MC4Pl0
设计自己的 Promise
如何设计 Promise,我们既然要做 Promise 的 demo,那我们最终产物的使用方式要和 Promise 是一致的。所以我们要先思考清楚我们产物的用法。(ps: 这体现了从上向下的设计思路。先设计大的框架,定义好使用方法,再想办法实现。)
// 我们的文件
import OasisPromise from './oasis-promise.js'
// 使用方式
new OasisPromise ((reslove, reject) => {
console.log('registry')
setTimeout(() => {
console.log('reslove callback')
reslove('resloved')
}, 0)
}).then((value) => {
console.log('then value', value)
return value
})
基于上述设计中的使用方式,我们首先要考虑链式调用如何实现,再 JavaScript 中,链式调用通过对象的方式可以实现。如下例:
// oasis-promise.js
class OasisPromise {
constructor() {}
then() {
return this
}
}
然后我们那可以写:
new OasisPromise().then()
继续扩充我们的 OasisPromise 类。由其用法我们知道,在构造函数中需要传一个方式,此方法包含两个内置的函数(reslove,reject)作为参数。我们先来实现这一步:
// oasis-promise.js
class OasisPromise {
constructor(action) {
if(action) {
action(this.reslove.bind(this), this.reject.bind(this))
}
}
reslove(value) {
console.log(value)
}
reject(error) {
console.error(error)
}
then(action) {
console.log('then')
return this
}
}
new OasisPromise((reslove,reject) => {
reslove('outter')
}).then()
new Promise().then()
reslove 要做的事情是用此阶段的 value,传给下一个 then 中的 handler,所以,在 then 方法调用的时候要能存下 then 方法的 handler,这里用了 handlers 数组。
// oasis-promise.js
class OasisPromise {
constructor(action) {
this.errorHanlders = () => {}
this.hanlders = []
if(action) {
action(this.resloveEvent.bind(this), this.rejectEvent.bind(this))
}
}
resloveEvent(value) {
let nextValue = value
try {
this.hanlders.forEach(handler => {
nextValue = handler(nextValue)
})
} catch (e) {
this.hanlders = []
this.rejectEvent(e)
}
}
rejectEvent(error) {
this.errorHandlers(error)
}
then(thenHandler) {
this.hanlders.push(thenHandler)
return this
}
catch(err) {
this.errorHandlers = err
return this
}
}
在上面这段代码的测试 demo 如下:
// Hi Oasis
new OasisPromise((reslove, reject) => {
setTimeout(() => {
reslove('Hi Oasis')
} ,0)
}).then((value)=> console.log(value))
// 无输出,因为 reslove 不是异步,导致then方法还未执行,所以 handlers 为空数组
new OasisPromise((reslove, reject) => {
// setTimeout(() => {
reslove('Hi Oasis')
// } ,0)
}).then((value)=> console.log(value))
// reslove 在这里是异步的
new Promise((reslove, reject) => reslove('Hi Oasis'))
.then((value) => console.log(value))
可以看出,promise 的方式还是要写很多模板代码,这时我们可以通过 async await 来完成同样的效果。 async await 在 nodejs 中会编译为 promise 的形式,所以 async 和 await 要配对,否则在翻译的时候找不到 then 的上下文。
为了看到效果,可访问如下链接: babel online
附加内容:下面代码的 AST 是什么样子的?可以通过 https://astexplorer.net/ 来观察