跳到主要内容

从0开始手写一个promise

首先回忆一下promise使用:

  new Promise((resolve, reject) => {
resolve(123)
}).then(res => {
console.log('success'+res);
}, err => {
console.log('fail'+err);
})

分析:

  1. new Promise(fn),首先实例化了一个名为promise的构造函数,传入了一个方法: fn
    1. fn接收两个形参:resolve,reject
    2. fn函数体内代码执行,异步代码执行完成,执行接收的实参resolve/reject
  2. 执行promise链上的then方法,传入两个方法作为实参 p.then(successFn, failFn)
  3. successFn/failFn有一个方法会执行,判断依据是调用了resolve还是reject

low版实现:

    class P {
constructor(runFn) {//构造函数默认执行
this.status = "pending";//声明默认状态
this.value//声明一个闭包变量
let resolve = res => {//执行函数体更改状态的实参 --- 更改内部的状态 接收执行更改状态传入的实参
if (this.status === 'pending') {//状态只能更改一次 默认状态才能更改
this.status = 'fulfilled'
this.value = res
}
}
let reject = rej => {
if (this.status === 'pending') {
this.status = 'rejected'
this.value = rej
}
}
runFn(resolve, reject)//执行函数体 传入两个更改状态的参数
}
then(resolveFn, rejectFn) {//promise().then(fn1, fn2)接收的两个回调
switch (this.status) {//判断状态执行不同回调
case "fulfilled":
resolveFn(this.value)// 将执行函数体 更改状态值时的形参作为实参传入回调
break;
case "rejected":
rejectFn(this.value)
break;

default:
break;
}
}
}

大概用起来好像是没什么毛病了。。。但是!!!(就怕但是😭)

    new P((resolve, reject) => {
// resolve(1234)
setTimeout(() => { // !!! 这样实现有个问题 先执行then方法是无法执行内部更改状态的方法的
resolve(1234)
}, 1000);
}).then(res => {
console.log(res);
}, err => {
console.log(err);
})

如果promise传入fn执行真的是异步来更改状态的,可能就是:
promise => fn() => then() => resolve/reject。
假设用setTimeout来模拟异步,可以知道setTimeout作为宏任务是在下一轮的event Loop执行栈中来执行的,而then方法属于调用P构造函数内的一个方法,所以会先于resolve/reject状态更改执行,内部也就还是pending状态。

解决真·异步的问题: 借鉴发布订阅模式的思路:如果执行then的时候没有进入结束状态,可以在P中订阅一个终止的状态(包括成功和失败)传入要执行的方法,然后在异步结束状态的时候更改状态时候(这种情况--很简单使用其实内部状态已经没有影响了),resolve/reject方法内部将分别订阅的任务列表中的方法遍历执行

   /* 
顺序应该是
如果执行then时状态还是 pending 先订阅 将将要执行的方法传入订阅列表
状态更改后 执行发布 执行订阅队列的方法 (发布订阅的拆解)
*/
class P {
constructor(runFn) {
this.status = "pending"; //声明默认状态
this.value; //声明一个闭包变量

this.onResolvedCallbacks = [];//成功的订阅列表 -- 解决异步then问题
this.onRejectedCallbacks = [];//失败的
let resolve = res => {
//执行函数体更改状态的实参 --- 更改内部的状态 接收执行更改状态传入的实参
if (this.status === "pending") {
//状态只能更改一次 默认状态才能更改
this.status = "fulfilled";
this.value = res;
this.onResolvedCallbacks.forEach(cb => cb());
}
};
let reject = rej => {
if (this.status === "pending") {
this.status = "rejected";
this.value = rej;
this.onRejectedCallbacks.forEach(cb => cb());
}
};
runFn(resolve, reject); //执行函数体 传入两个更改状态的参数
}
then(resolveFn, rejectFn) {
switch (
this.status //判断状态执行不同回调
) {
case "fulfilled":
resolveFn(this.value); // 将执行函数体 更改状态值时的形参作为实参传入回调
break;
case "rejected":
rejectFn(this.value);
break;

default:
this.onResolvedCallbacks.push(() => {
resolveFn(this.value);
});
this.onRejectedCallbacks.push(() => {
rejectFn(this.value);
});
break;
}
}
}

较为完善实现

日常更多用到的写法可能是链式调用,而不是在then里面传入两个状态回调,而且!!!promise是支持链式调用的new P().then().then().catch()。 看看平常怎么写

new Promise((resolve, reject) => {
reject('aasdfgfdg哈哈哈我专门的')
}).then(res => {
return ++res
}).then(res => {

}).catch(err => {
console.log(err)
})

// ---------------------------

new Promise((resolve, reject) => {
resolve(1)
}).then(res => {
return ++res
}).then(res => {
aasdfgfdg哈哈哈我专门的
}).catch(err => {
console.log(err)
})

可以看到区别:

  1. then可以不传两个参数。(处理一下resolveFn,rejectFn)
  2. then之后还可以then,说明应该then应该是有一个返回的函数 柯里化 (then方法应该能返回一个新的P)
  3. reject可以直接跳楼到catch方法
  4. catch可以捕获到then执行失败的代码(如何捕获代码呢?try catch)