学着写一个简单的promise
定义
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
说在前面
这是一个简版非规范的Promise实现,其中前置知识点比较重要,理解了前置知识点的内容,也就了解了Promise最基本的原理。
前置知识
这小节的核心是:把函数名当变量用。
以下三点理解之后,就能够实现简单的Promise了。
类型为Function的变量,可以像函数一样调用。
举个栗子
let myFn = function () {};
myFn();
和下面的代码效果是一样的
function myFn() {
// code here...
}
myFn();
函数数组,即函数里的每一个元素都是函数,可以用遍历调用。
let fnArray = [];
let fn1 = () => { console.log(1) };
let fn2 = () => { console.log(2) };
fnArray.push(fn1,fn2);
fnArray.forEach(cb => cb());
运行结果是
1
2
函数可以当作参数传递
function showYouFn(fn1, fn2) {
// 调用
fn1();
fn2();
}
let myFn1 = () => { console.log(3); };
let myFn2 = () => { console.log(4); };
showYouFn(myFn1, myFn2);
回想一下:把函数名当变量用。
实现简单模型
下面是一个简陋得不像Promise的Promise实现
function MyPromise(fn) {
function resolve(value) {
console.log(value);
}
function reject(value) {
console.log(value);
}
fn(resolve, reject);
}
现在你就可以用上自定义的Promise了
new MyPromise((resolve, reject) => {
setTimeout(()=>{
resolve('你将看到两秒后的我');
}, 2000);
});
将会在2秒后输出
你将看到两秒后的我
解释一下整体代码:
MyPromise 中的参数 fn是需要用户传入的自定义函数(该函数需要接收两个参数)。
MyPromise 中的内部还有两个函数resolve和reject,其中 resolve 表示用户的异步任务成功时应该调用的函数,reject 表示用户的异步任务失败时该调用的函数。
那用户如何调用 resolve 和 reject 呢?
很简单,把两个函数当作 fn的参数传递出去即可。
所以 MyPromise 内部在调用 fn 时会把 resolve 和 reject当作参数传递给 fn。
然后用户在自定义函数内调用 resolve 或 reject 来通知 MyPromise 异步任务已经执行完了。
通过上面的代码可以发现一个问题,我们不知道Promise的异步任务进行到哪一步了、是成功还是失败了。
所以增加三个状态用来标识一个Promise的异步任务进行到何种程度了。
pending、resolved、rejected 分别表示 执行中、已完成、已失败。
然后通过观察用户调用的是 resolve 还是 reject 可以判断当前Promise的状态。
那么会有三种情况:
- 在用户调用 resolve 或 reject 之前状态是 pending
- 用户调用 resolve 时,状态将变为 resolved
- 用户调用 reject 时,状态将变为 rejected
下面进行代码的改造,定义了三个常量表示状态以及一个变量 state 用来存储当前状态。
并且当 resolve 被调用时将 state 修改为 resolved 。
const PEDNING="pending";//执行状态
const RESOLVED='resolved';//以完成;
const REJECTED='rejected';//以失败
function MyPromise(fn){
const that=this
//初始状态为执行中,pending
this.state=PEDNING;
function resolve(value){
console.log(value)
that.state=RESOLVED;
}
function reject(err){
console.log(err)
that.state=REJECTED;
}
fn(resolve,reject);
}
OK,现在已经能知道Promise当前所处的状态了,但是任务完了得拿到结果吧,MyPromise 的 resolve 被调用,那也只是MyPromise知道任务完成了,用户还不知道呢。
所以我们需要回调函数告诉用户,是的,其实就是回调函数。
这时候就轮到 then 方法出场了,用户通过then方法传入回调函数, MyPromise 将在成功调用 resolve 时调用用户传入的回调函数。
开始改造代码,MyPromise 内部需要变量存储回调函数,then 方法的作用就是将用户传入的回调函数赋予 MyPromise 内的变量。
所以 then 方法长这样,接收两个参数,一个是成功时的回调函数,一个是失败时的回调函数
const PEDNING="pending";//执行状态
const RESOLVED='resolved';//以完成;
const REJECTED='rejected';//以失败
function MyPromise(fn){
const that=this
//初始状态为执行中,pending
this.state=PEDNING;
//两个储存回调函数的变量
this.resolvedCallback;
this.rejectedCallback;
function resolve(value){
that.state=RESOLVED;
that.resolvedCallback && that.resolvedCallback(value);
}
function reject(err){
that.state=REJECTED;
that.rejectedCallback && that.rejectedCallback(err);
}
fn(resolve,reject);
}
MyPromise.prototype.then=function(onFulfilled,onRejected){
this.resolvedCallback = onFulfilled;
this.rejectedCallback = onRejected;
}
是的,一个简版Promise几乎大功告成,让我们再试试在浏览器执行如下代码(注意我删除了 resolve 和 reject 里的console语句);咱们来使用一下:
(function(){
const PEDNING="pending";//执行状态
const RESOLVED='resolved';//以完成;
const REJECTED='rejected';//以失败
function MyPromise(fn){
const that=this
//初始状态为执行中,pending
this.state=PEDNING;
//两个储存回调函数的变量
this.resolvedCallback;
this.rejectedCallback;
function resolve(value){
that.state=RESOLVED;
that.resolvedCallback && that.resolvedCallback(value);
}
function reject(err){
that.state=REJECTED;
that.rejectedCallback && that.rejectedCallback(err);
}
fn(resolve,reject);
}
MyPromise.prototype.then=function(onFulfilled,onRejected){
this.resolvedCallback = onFulfilled;
this.rejectedCallback = onRejected;
}
new MyPromise((resolve,reject)=>{
setTimeout(()=>{
resolve('我是结果')
},4000);
}).then((value)=>{
console.log(value)
})
})()
通过匿名函数和函数自执行,形成局部作用域,保护里面的变量;
上面的代码,用法上已经和Promise长得差不多了,但是如果我们多次调用 then 方法呢?
是的,只有最后一个 then 方法里的回调函数能执行,这当然没法满足我们的需要。
于是,将两个回调函数改成函数数组(请回想一下前置知识),并在状态更改时遍历调用回调函数。
改造后的代码如下:
(function(){
const PEDNING="pending";//执行状态
const RESOLVED='resolved';//以完成;
const REJECTED='rejected';//以失败
function MyPromise(fn){
const that=this
//初始状态为执行中,pending
this.state=PEDNING;
//两个储存回调函数的变量,注意,变成了数组
this.resolvedCallbackList=[];
this.rejectedCallbackList=[];
function resolve(value){
that.state=RESOLVED;
that.resolvedCallbackList && that.resolvedCallbackList.forEach(cbFn =>cbFn(value));
}
function reject(err){
that.state=REJECTED;
that.rejectedCallbackList && that.rejectedCallbackList.forEach(cbFn =>cbFn(err));
}
fn(resolve,reject);
}
MyPromise.prototype.then=function(onFulfilled,onRejected){
this.resolvedCallbackList.push(onFulfilled);
this.rejectedCallbackList.push(onRejected);
// 这里是为了链式调用,所以要返回this,即myPromise.then().then().then()...
return this
}
new MyPromise((resolve,reject)=>{
setTimeout(()=>{
resolve('经过5秒出现')
},5000)
}).then((val)=>{
console.log(val+'第一次出现的value')
}).then((val)=>{
console.log(val+'第二次出现的value')
})
})()
上面已经是简版Promise的实现了。
但是我们还可以更完善一点,增强 MyPromise 的健壮性。
例如,若用户自定义函数在执行过程中发生了错误,会中断程序的执行,于是我们增加try...catch...语句,并在发生错误时主动执行reject函数告知用户。
try {
fn(resolve, reject);
} catch (e) {
reject(e);
}
又或者,对参数进行校验,状态进行判断等,以 then为例,若用户传入的参数不是函数呢? 或者Promise的状态已经时rejected或resolved,此时调用then呢?
改造 then 后代码如下:
MyPromise.prototype.then = function(onFulfilled, onRejected) {
if(typeof onRejected !== 'function') {
onRejected = v => v;
}
if(typeof onFulfilled !== 'function') {
onFulfilled = v => { throw r };
}
const that = this;
if (that.state === PENDING) {
that.resolvedCallbacks.push(onFulfilled)
that.rejectedCallbacks.push(onRejected)
}
if (that.state === RESOLVED) {
onFulfilled(that.value)
}
if (that.state === REJECTED) {
onRejected(that.value)
}
}