Promise原理 渐进渐知
一、极简promise雏形
function Promise(fn) {
var value = null,
callbacks = []; //callbacks为数组,因为可能同时有很多个回调
this.then = function (onFulfilled) {
callbacks.push(onFulfilled);
return this;
};
function resolve(value) {
callbacks.forEach(function (callback) {
callback(value);
});
}
fn(resolve);
}
下面应用一下这个手写的Promise基本原理:
let p = new Promise(resolve => {
setTimeout(() => {
console.log('done');
resolve('5秒');
}, 5000);
}).then((tip) => {
console.log(tip);
})
运行结果如下:
约5秒钟后出现“done 5秒钟”

下面运用调试功能,看一下基本的运行逻辑:
在
let p = new Promise......
这一行上面打断点,调试后代码运行在此行上,点“步入”,进入构造函数
function Promise(fn)

callbacks数组为“undefined”

从第5步直接跳转至第6步,如下:

第7步之后跳到第8步,完成p实例化;第9步开始调用.then函数

从第11步可以看出,
(tip) => {
console.log(tip);
已经压栈进入callbacks数组中。

第12步完成.then函数的调用,第13步进入setTimeout异步函数的调用;

第14步,输出了“done”

第15步,进入resolve函数

第16步,value值为“5秒"

第17步,相当于第16步时,callback(value)调用的是:
(value) => {
console.log(value);

第18步,
(tip) => {
console.log(tip);
}
函数调试结束

第19步,forEach函数调用结束 ;

第20步,resolve函数调用结束 ;

第21步,setTimout函数调用结束 ;
至此,调试完毕!

综合上述:
上述代码很简单,大致的逻辑是这样的:
- 调用
then方法,将想要在Promise异步操作成功时执行的回调放入callbacks队列,其实也就是注册回调函数,可以向观察者模式方向思考; - 创建
Promise实例时传入的函数会被赋予一个函数类型的参数,即resolve,它接收一个参数value,代表异步操作返回的结果,当一步操作执行成功后,用户会调用resolve方法,这时候其实真正执行的操作是将callbacks队列中的回调一一执行;
首先 new Promise 时,传给 Promise 的函数设置定时器模拟异步的场景,因为setTimeout为异步,没有立即执行,而是紧接着调用 Promise 对象的 then 方法;调用then方法注册异步操作完成后的 onFulfilled(这一步也就相当于将匿名函数压入callbacks数组栈中);然后,又开始执行setTimeout异步函数,在异步函数setTimeout中,调用 resolve(value), 执行 then 方法注册的 onFulfilled。
then 方法注册的 onFulfilled 是存在一个数组中,可见 then 方法可以调用多次,注册的多个onFulfilled 会在异步操作完成后根据添加的顺序依次执行。如下:
function Promise(fn) {
var value = null,
callbacks = []; //callbacks为数组,因为可能同时有很多个回调
this.then = function (onFulfilled) {
callbacks.push(onFulfilled);
// return this;
};
function resolve(value) {
callbacks.forEach(function (callback) {
callback(value);
});
}
fn(resolve);
}
let p = new Promise(resolve => {
setTimeout(() => {
console.log('done');
resolve('5秒');
}, 5000);
})
p.then((tip) => {
console.log("then1",tip);
})
p.then((tip) => {
console.log("then2",tip);
})
运行结果如下:

上例中,要先定义一个变量 p ,然后 p.then 两次。而规范中要求,then 方法应该能够链式调用。实现也简单,只需要在 then 中添加 return this 即可。如下所示:
function Promise(fn) {
var value = null,
callbacks = []; //callbacks为数组,因为可能同时有很多个回调
this.then = function (onFulfilled) {
callbacks.push(onFulfilled);
return this; //this 指向调用这个方法的对象实例;
};
function resolve(value) {
callbacks.forEach(function (callback) {
callback(value);
});
}
fn(resolve);
}
let p = new Promise(resolve => {
setTimeout(() => {
console.log('done');
resolve('5秒');
}, 5000);
}).then((tip) => {
console.log("then1",tip);
}).then((tip) => {
console.log("then2",tip);
})
运行结果如下:

二、加入延时机制
上面 Promise 的实现存在一个问题:如果在 then 方法注册 onFulfilled 之前,resolve 就执行了,onFulfilled 就不会执行到了。比如上面的例子中我们把 setTimout 去掉:
function Promise(fn) {
var value = null,
callbacks = []; //callbacks为数组,因为可能同时有很多个回调
this.then = function (onFulfilled) {
callbacks.push(onFulfilled);
return this; //this 指向调用这个方法的对象实例;
};
function resolve(value) {
callbacks.forEach(function (callback) {
callback(value);
});
}
fn(resolve);
}
let p = new Promise(resolve =>{
console.log('同步执行1');
resolve('同步执行2');
}).then((tip) => {
console.log("then1",tip);
}).then((tip) => {
console.log("then2",tip);
})
运行结果如下

执行结果显示,只有 "同步执行1" 被打印了出来,后面的”同步执行2“ 、"then1" 和 "then2" 均没有打印出来。再回去看下 Promise 的源码,也很好理解,在调用resolve函数 执行时,callbacks 还是空数组,还没有onFulfilled 注册上来,所以resolve、then1、then2都没有执行。
在前面的调试中已经看到,是先执行.then函数将其中的匿名函数注册到callbacks数组中,再调用的setTimeout函数中的resolve,因为callbacks数组中已经压入了匿名函数,所以resolve函数可以调用并执行。纵观整个Promise,执行顺序为then->setTimeout->resolve,这样就保证了Promise的顺利执行。
Promises/A+规范明确要求回调需要通过异步方式执行,用以保证一致可靠的执行顺序。因此要加入一些处理,保证在 resolve 执行之前,then 方法已经注册完所有的回调:
function Promise(fn) {
var value = null,
callbacks = []; //callbacks为数组,因为可能同时有很多个回调
this.then = function (onFulfilled) {
callbacks.push(onFulfilled);
return this; //this 指向调用这个方法的对象实例;
};
//改写resolve函数,增加setTimeout;
function resolve(value) {
setTimeout(function() {
callbacks.forEach(function (callback) {
callback(value);
});
}, 0)}
fn(resolve);
}
let p = new Promise(resolve =>{
console.log('同步执行1');
resolve('同步执行2');
}).then((tip) => {
console.log("then1",tip);
}).then((tip) => {
console.log("then2",tip);
})
运行结果如下

上述代码中,我们在 resolve 中增加定时器,通过 setTimeout 机制,将 resolve 中执行回调的逻辑放置到JS任务队列末尾,以保证先执行then方法的 onFulfilled注册完成,再执行resolve函数(注:这时callbacks数组中已经压入then1和then2两个匿名函数,resolve函数就可以调用并使用了)。
但是这样依然存在问题,在 resolve 执行后,再通过 then 注册上来的 onFulfilled 都没有机会执行了。如下所示,我们加了延迟后,then1 和 then2 可以打印出来了,但下例中的 then3 依然打印不出来。所以我们需要增加状态,并且保存 resolve 的值。
function Promise(fn) {
var value = null,
callbacks = []; //callbacks为数组,因为可能同时有很多个回调
this.then = function (onFulfilled) {
callbacks.push(onFulfilled);
return this; //this 指向调用这个方法的对象实例;
};
//改写resolve函数,增加setTimeout;
function resolve(value) {
setTimeout(function() {
callbacks.forEach(function (callback) {
callback(value);
});
}, 0)}
fn(resolve);
}
let p = new Promise(resolve =>{
console.log('同步执行1');
resolve('同步执行2');
}).then((tip) => {
console.log("then1",tip);
}).then((tip) => {
console.log("then2",tip);
})
setTimeout(() => {
p.then(tip => {
console.log('then3', tip);
})
});
运行结果如下

三、增加状态
为了解决上一节抛出的问题,我们必须加入状态机制,也就是大家熟知的pending、fulfilled、rejected。
Promises/A+规范中的2.1Promise States中明确规定了,pending可以转化为fulfilled或rejected并且只能转化一次,也就是说如果pending转化到fulfilled状态,那么就不能再转化到rejected。并且fulfilled和rejected状态只能由pending转化而来,两者之间不能互相转换。如下图所示:

增加状态后的Promise如下
function Promise(fn) {
var state = 'pending',
value = null,
callbacks = [];
this.then = function (onFulfilled) {
if (state === 'pending') {
callbacks.push(onFulfilled);
return this;
}
onFulfilled(value);
return this;
};
function resolve(newValue) {
value = newValue;
state = 'fulfilled';
setTimeout(function () {
callbacks.forEach(function (callback) {
callback(value);
});
}, 0);
}
fn(resolve);
}
let p = new Promise((resolve,reject) => {
setTimeout(()=>{
resolve('morrain');
},1000)
}).then(value => {
console.log('value=',value);
}).then(value => {
console.log('value=',value);
})
运行结果如下

resolve 执行时,会将状态设置为 fulfilled ,并把 value 的值存起来,在此之后调用 then 添加的新回调,都会立即执行,直接返回保存的value值。
增加代码:
setTimeout(() => {
p.then(tip => {
console.log('then3', tip);
})
});
改造代码如下
function Promise(fn) {
var state = 'pending',
value = null,
callbacks = [];
this.then = function (onFulfilled) {
if (state === 'pending') {
callbacks.push(onFulfilled);
return this;
}
onFulfilled(value);
return this;
};
function resolve(newValue) {
value = newValue;
state = 'fulfilled';
setTimeout(function () {
callbacks.forEach(function (callback) {
callback(value);
});
}, 0);
}
fn(resolve);
}
let p = new Promise((resolve,reject) => {
setTimeout(()=>{
resolve('morrain');
},1000)
}).then(value => {
console.log('value=',value);
}).then(value => {
console.log('value=',value);
})
//改造增加的代码如下:
setTimeout(() => {
p.then(tip => {
console.log('then3', tip);
})
});
运行结果如下
从上述代码的调试可以看到,增加的代码
setTimeout(() => {
p.then(tip => {
console.log('then3', tip);
})
});
此时的state已经是fulfilled状态了,所以,走的是
onFulfilled(value);
return this;
这个分支并执行的。
至此,一个初具功能的Promise就实现好了,它实现了 then,实现了链式调用,实现了状态管理等等。但仔细想想,链式调用的实现只是在 then 中 return 了 this,因为是同一个实例,调用再多次 then 也只能返回相同的一个结果,这显然是不能满足我们的要求的。下面,讲述如何实现真正的链式调用。
三、链式调用
通常,我们希望的链式调用是如下这样的:
const p1 = Promise.resolve(42);
const p2 = p1.then(value => value + 1); // 返回 43
p2.then(console.log); // 输出: 43
每个 then 注册的 onFulfilled 都返回了不同的结果,层层递进,很明显在 then 方法中 return this 不能达到这个效果。引入真正的链式调用,then 返回的一定是一个新的Promise实例。
真正的链式 Promise 是指在当前 Promise 达到 fulfilled 状态后,即开始进行下一个 Promise(后邻 Promise)。那么我们如何衔接当前 Promise 和后邻 Promise 呢?(这是理解 Promise 的难点)
链式调用的实现
就是在then方法里面return一个promise就好啦。

浙公网安备 33010602011771号