在了解实现之前最好知道promise的使用,不会的话可以先去查看相关文档。除此之外,还需要了解事件循环,了解宏任务与微任务。
实现
该Promise实现只考虑resolve,reject同resolve的思路差不多就不参与进来添加阅读成本了。
// 使用示例:
new Promise1(exec).then(fullfill).then(fullfill2)
function exec(resolve) {
console.log(1)
setTimeout(() => {
resolve(1)
},3000)
}
function fullfill(val) {
console.log(val,1)
return new Promise1((resolve) => {
setTimeout(() => {
resolve(2)
},3000)
})
}
function fullfill2(val) {
console.log(val,2)
}
我们先不考虑内部具体干了什么,根据示例可以知道Promise与then分别接收了一个exec与fullfill两个外部传来的函数,而resolve,与then一定是在结构内部的,因此可以首先大致推出一个Promise的结构
function Promise1() {
function resolve(){}
this.then = function() {
}
}
在使用Promise的时候,可以发现在使用过程中通过Promise接收到的exec函数是同步执行的,且exec接收了resolve这个函数,而另一个fullfill则接受了一个val参数并且自身作为then的参数,所以可以进一步得到下面的情况
function Promise1(exec) {
function resolve() {}
this.then = function(fullfill(val)) {
}
exec(resolve)
}
现在我们来梳理一下promise流程顺序,先是执行了内部的exec函数并以resolve为参传入,然后通过resolve触发then函数中传入的fullfill函数。
可以发现是在then函数执行的时候先将传入的fullfill函数储存起来,然后等到resolve触发时再将储存的fullfill函数取出并执行,而在这个时候也正好能将传入resolve函数的val参数传给fullfilledFunc使用。
所以我们需要一个数组将fullfill函数在then执行的时候存起来,并且在resolve中取出调用,如下
function Promise1(exec) {
let fullfillStack = []
function resolve(val) {
fullfillStack.forEach((fullfill) => {
fullfilledFunc(val)
})
}
this.then = function(fullfill) {
fullfillStack.push(fullfill)
}
exec(resolve)
}
以上代码明显都是同步进行的,但是resolve肯定只能在then函数调用之后,否则fullfillStack中可能什么都没有就执行resolve了。所以我们需要一个计时器来实现异步延迟的效果。
function Promise1(exec) {
let fullfillStack = []
function resolve(val) {
setTimeOut(() => {
fullfillStack.forEach((fullfill) => {
fullfill(val)
})
},0)
}
this.then = function(fullfill) {
fullfillStack.push(fullfill)
}
exec(resolve)
}
我们知道Promise是加入了微任务的,原生Promise在浏览器中是通过MutationObserve这个API实现的,而在Node中则是通过process.nextTick实现的,我们先采用Node(因为简单哈哈),浏览器版本在最后。
function Promise1(exec) {
let fullfillStack = []
function resolve(val) {
process.nextTick(() => {
fullfillStack.forEach((fullfill) => {
fullfill(val)
})
})
}
this.then = function(fullfill) {
fullfillStack.push(fullfill)
}
exec(resolve)
}
我们知道在then执行之后返回的是一个promise,并且可以如示例中采用调用链的方式,此时就需要借助状态来确保对结果的处理以及调用链的实现,见下:
function Promise1(exec) {
let fullfillStack = []
let state = 'pending'
let data = null
function resolve(val) {
process.nextTick(() => {
state = 'fullfilled'
data = val
fullfillStack.forEach((fullfill) => {
fullfill(data)
})
})
}
this.then = function(fullfill) {
if(state === 'pending') {
fullfillStack.push(fullfill)
return this
} else if (state === 'fullfilled'){
fullfill(data)
}
}
exec(resolve)
}
但是这样会发现示例中的结果为:
11
12
第二个then中的val仍是第一个then中的val,这是因为每次都返回了promise本身而不是一个新的promise,实际上这二者应该是毫无关系的,所以我们应该返回一个新的promise
function Promise1(exec) {
let fullfillStack = []
let state = 'pending'
let data = null
function resolve(val) {
process.nextTick(() => {
state = 'fullfilled'
data = val
fullfillStack.forEach((fullfill) => {
fullfill(data)
})
})
}
this.then = function(fullfill) {
return new Promise1(resolve => {
function success() {
//对传入的fullfill判断并对data处理
let result = typeof fullfill === 'function' ? fullfill(data) : data
//判断result是否为promise
if(result instanceof Promise1 && typeof result['then'] === 'function') {
result.then(function(val){
resolve(val)
})
} else {
resolve(result)
}
}
if(state === 'pending') {
fullfillStack.push(success)
} else if (state === 'fullfilled'){
success(data)
}
})
}
exec(resolve)
}
由于要返回一个新的promise,所以我们直接在then函数里生成Promise实例,同时我们需要对then函数接收到的参数进行判断,所以我们用一个函数将这些处理封装起来。如果是一个函数,我们才能让它去对resolve传过来的值进行处理。对于处理完之后的结果,我们同样需要再进行处理,因为结果有可能是个promise。
其他的地方基本上就和上一步骤的差不多了,注意在状态为pending的时候,应该推入success而不是原来的fullfill了。
到这promise也就基本上实现了。
浏览器版实现:
function resolve(val) {
//make microRequest
let callback = function() {
fullfilledStack.forEach((fullfilledFn) => {
fullfilledFn(val)
})
let child = document.createElement('div')
let config = { childList: true }
let observer = new MutationObserver(callback)
observer.observe(targetNode, config)
let targetNode = document.createElement('div')
function triggerCallback(targetNode, child) {
targetNode.children.length > 0 ? targetNode.removechild(child):targetNode.appendChild(child)
}
triggerCallback(targetNode, child)
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。