接触过JS的开发人员应该都有用过Promise处理异步编程,它在语法上非常直观的用“then”去对当下所要做得执行和在期之后所要执行的代码做了封装。
function test() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('after one second');
}, 1000);
})
return promise;
}
test().then(res => {
console.log(res);
})
console.log("before one second");
// "before one second"
// "after one second"
对没有接触过异步编程的工程师来讲,这看起来似乎有魔术般的效应,仿佛是将时间做了停顿。不过花点心思去想Promise的内部机制,大家就会发现,它的核心在于巧妙地应用高阶函数的理念将异步操作进行了封装。如果用最轻量的方法趋势线上图的Promise功能,其实几行代码便可以解决。
function Promice(fn) {
var self = this
self.status = 'pending' // Promise当前的状态
self.data = undefined // Promise的值
self.onResolvedCallback = undefined //回调函数
function resolve(value) {
if (self.status === 'pending') {
self.status = 'resolved'
self.data = value
if(self.onResolvedCallback) {
self.onResolvedCallback(value)
}
}
}
try {
fn(resolve)
} catch (e) {
reject(e)
}
}
在这里我将reject抓错机制去除,针对去看Promise是如何"控制"异步操作的。上图需要注意的是两个重点:1. status状态去跟踪异步操作是否完成。2. resolve函数要做的就是更新状态并且调用用户指定的fn回调函数。有人可能会问:“如果只是单纯的更新状态,如何保证异步操作已经完成呢?”。这里要注意的是,从Promise得操作说明上,resolve永远只会在异步回调函数里出现,所以当resolve启动时,必然表示异步操作已完成。
有人会注意到onResolvedCallback赋值并没有在代码里实现。这表示在resolve被调用之前,还会有其他的操作执行,也就是"then"函数。
Promice.prototype.then = function (onResolved) {
var self = this
var promise2
if (self.status === 'resolved') {
return promise2 = new Promice(function (resolve, reject) {
var x = onResolved(self.data)
if (x instanceof Promice) {
x.then(resolve, reject)
}
resolve(x)
})
}
if (self.status === 'pending') {
return promise2 = new Promice(function (resolve, reject) {
self.onResolvedCallback = function (value) {
var x = onResolved(self.data)
if (x instanceof Promice) {
x.then(resolve, reject)
}
}
})
}
}
then函数其实在一开始就被调用了,它的责任是去查看status。如果status表明一步操作已经完成,但就直接调用fn。如果还在待定(pending),则将函数赋值给onResolveCallback。当resolve触发时必会做处理。
总结:Promise会给很多人带来一种时间被控制的假象,但底层核心逻辑并不复杂。这里需要注意的是then函数很容易被视为一个异步操作结束后调用的函数,但这明显是个错误的想法。then通常在异步操作之前已经调用,他的任务往往是将传入的回调函数放到resolve可以获取到的作用域。虽然Promise产生的假象能让我们更直观的去开发,但也需要了解的是它的底层实现。