本文思路的开始是模拟实现Promise,因此先来探讨Promise。vue
Promise 是异步编程的一种解决方案,最先是社区为了避免在回调地狱里沉沦而提出,ES6将其写进了语言标准,统一了用法,原生提供了Promise对象。node
Promise 简单说就是一个容器,里面保存着一个异步操做结束后的结果。git
promise.then(data => console.log(data))
// then 表示异步操做完成
// data 就是结果
复制代码
好比这里有一个异步操做,用setTimeout模拟:github
let data = null
setTimeout(function() { data = 1 }, 1000)
复制代码
当data发生改变后,我想“马上”输出。编程
第一种方法(显而易见):api
setTimeout(function () {
let data = 1
console.log(data)
}, 1000)
复制代码
第二种方法(略显而易见):promise
setTimeout(function() {
let data = 1
setTimeout(() => console.log(data), 0)
}, 1000)
复制代码
为何第二种方式也能取到?浏览器
先来读一遍教科书般(哪都能看到)的《JS事件循环机制》:bash
1.全部任务都在主线程上执行,造成一个执行栈。
2.主线程以外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
3.一旦"执行栈"中的全部同步任务执行完毕,系统就会读取"任务队列"。那些对应的异步任务,结束等待状态,进入执行栈并开始执行。
4.主线程不断重复上面的第三步。
复制代码
而后咱们来看第二种方法。异步
setTimeout(function() {
// 这个function内,对应一个执行栈
let data = 1 // 同步任务
setTimeout(() => console.log(data), 0) // 一个异步任务,执行到这时,会将该异步任务先放进"任务队列"
// 同步任务 "let data = 1" 执行完,执行异步任务
}, 1000)
复制代码
这里调整两行代码顺序,结果是同样的。
setTimeout(function() {
setTimeout(() => console.log(data), 0)
let data = 1
}, 1000)
复制代码
可是promise.then(data => console.log(data))
结构不太同样,这里用一个回调函数取得异步操做后的结果。
他是怎么作到的。
// 简易 Promise 定义
function Promise(excutor) {
this.callback = function() {}
let that = this
function resolve(value) {
// 放置一个异步任务,在异步任务执行回调
setTimeout(() => that.callback(value), 0)
}
excutor(resolve)
}
// then 方法只是保存callback函数
Promise.prototype.then = function (callback) {
this.callback = callback
}
const promise = new Promise(function(resolve) {
setTimeout(() => resolve(1), 1000)
})
promise.then(data => console.log(data))
复制代码
先把data => console.log(data)
函数保存,再在resolve接收到异步数据后执行。
这里能按照这样的前后顺序,跟上面第二种方法道理是同样的。
setTimeout(function() {
setTimeout(() => console.log(data), 0) // => setTimeout(() => that.callback(value), 0) => 放置异步任务
let data = 1 // => promise.then(data => console.log(data)) => 都是同步任务
}, 1000)
复制代码
这大体是promise的基本原理,以上咱们使用setTimeout来实现异步任务,从而达到模拟promise的效果。
在浏览器中,异步任务大体有:
宏任务 (MacroTask):setTimeout、setInterval、I/O、UI渲染
微任务 (MicroTask):Promise、MutationObsever
复制代码
在node环境中,异步任务大体有:
宏任务 (MacroTask):setTimeout、setInterval、I/O、setImmediate
微任务 (MicroTask):Promise、process.nextTick
复制代码
在一个执行栈中,会先执行同步代码,遇到异步任务,会将其压到"任务队列"(task queue)中。
分别有"宏任务队列"、"微任务队列"。同步代码执行完,将"微任务队列"首任务的回调加入执行栈,执行。
循环"微任务队列",直到队列空。再循环"宏任务队列"。
不一样的"宏任务"、"微任务"之间还有优先级,会影响其执行顺序。
回到Promise。
引擎里实现的Promise,会建立一个"微任务"。而且提供了一些api,Promise会尊崇一些规范。
因此,咱们所说的模拟实现Promise,能够这样拆分。
异步任务咱们要看执行环境,有哪些可选。
这里面最难理解的是Promise解决过程[[Resolve]](promise, x)。
Promise 解决过程是一个抽象的操做,其需输入一个 promise 和一个值,
咱们表示为 [[Resolve]](promise, x),若是 x 有 then 方法且看上去像一个 Promise ,
解决程序即尝试使 promise 接受 x 的状态;不然其用 x 的值来执行 promise 。
复制代码
能够看到Promise A+规范是很细节的,要想彻底经过他的测试,必须知足他的全部约束。
这里有篇文章实现的挺好---Promise原理讲解 && 实现一个Promise对象 (遵循Promise/A+规范)。
在他的resolve
方法里能够看到,是用setTimeout
来模拟。
其实最好是先用微任务模拟,若是环境不支持,再降级为宏任务。
这个思路相似与vue
中的nextTick
,源码传送门。
能够看到他的降级策略是:
Promise -> MutationObserver -> setImmediate -> setTimeout
复制代码
nextTick
的做用是在数据渲染完成后执行,它的道理是在当前执行栈底放入一个异步任务。
相关参考资料: