前言
promise是ES6新推出的一种优雅的异步任务的解决方法。相信大家对它是既熟悉又陌生,熟悉是因为我们在开发的时候经常都会用到它,陌生是因为当我们谈到手撕promise的时候却感到无从下手,今天就带大家如何一步一步地实现promise。
1、new Promise()
前置知识
- new Promise中传入一个函数,这个函数是立即执行的。
- 传入的函数有两个参数resolve和reject,执行后会改变promise状态。
- promise有三种状态pending、fulfilled、rejected。状态一旦发生改变,就不会再变。
我们先看下这三种状态下的promise都长什么样:
let p1 = new Promise((resolve, reject) => {});
let p2 = new Promise((resolve, reject) => {
resolve("resolve");
});
let p3 = new Promise((resolve, reject) => {
reject("reject");
});
console.log(p1);//pending
console.log(p2);//fulfilled
console.log(p3);//rejected
pending状态
fulfilled状态
rejected状态
从上面可以得到的信息有:
- PromiseState代表当前Promise的状态。
- PromiseResult代表当前Promise状态改变时传入的参数,默认为undefined。
为了代码简洁,这里就使用class关键字来实现。
class MyPromise {
//Promise三种状态
static PENDING = "pending";
static FULLFILLED = "fullfilled";
static REJECTED = "rejected";
constructor(fn) {
this.PromiseState = MyPromise.PENDING;// 当前promise的状态
this.PromiseResult = undefined;// promise收到的参数
fn();// 传入的函数立即执行,这里还未传入resolve与rejecte函数
}
}
2、resolve和rejecte函数
前置知识
- 这两个函数是用于改变当前promise状态的。
- promise状态只有两种变化。pending-->fulfilled或pending-->rejected
- resolve调用promise状态从pending变为fulfilled,rejected调用promise状态从pending变为rejected。
class MyPromise {
//Promise三种状态
static PENDING = "pending";
static FULLFILLED = "fullfilled";
static REJECTED = "rejected";
constructor(fn) {
this.PromiseState = MyPromise.PENDING;// 当前promise的状态
this.PromiseResult = undefined;// promise收到的参数
/*
这里由于resolve和rejecte函数是传到外部使用的,this会指向undefined。
导致resovle和rejecte无法通过this修改promise实例的状态
所以在传出去的时候先要绑定一下this,让它一直指向当前promise实例
*/
const RESOLVE = this.resolve.bind(this);
const REJECT = this.reject.bind(this);
fn(RESOLVE,REJECT);// 传入的函数立即执行
}
resolve(res) {
// 状态只能由pending变为其他,如果不是pending,说明状态已经改变,不再往下执行。
if (this.PromiseState !== MyPromise.PENDING) return;
//改变状态
this.PromiseState = MyPromise.FULLFILLED;
//传递参数
this.PromiseResult = res;
}
reject(res) {
if (this.PromiseState !== MyPromise.PENDING) return;
this.PromiseState = MyPromise.REJECTED;
this.PromiseResult = res;
}
}
这里可以测试一下,resolve和rejected函数传出去时不绑定this会怎样
输出结果,this指向undefined,导致resolve或rejecte内部无法通过this访问到promise实例
如果在new Promise时直接抛出错误
promise的状态会直接变为rejected
所以我们可以在函数内添加try catch捕捉错误,只要捕捉到错误直接调用rejecte函数
3、then函数
前置知识:
- then接收两个参数,一个是成功的回调函数,一个是失败的回调函数。两个参数都是函数。
- 成功的回调函数的参数为resolve传入的值,失败的回调函数是rejecte传入的值。
then(onFulfilled, onRejected) {
// 先判断传入的值是否为函数,如果不是先包装为函数
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : () => {};
onRejected = typeof onRejected === "function" ? onRejected : () => {};
if (this.PromiseState === MyPromise.FULLFILLED) {
// 如果状态为fulfill就调用onFulfilled函数,并将结果传入
onFulfilled(this.PromiseResult);
} else if (this.PromiseState === MyPromise.REJECTED) {
// 如果状态为fulfill就调用onrejected函数,并将结果传入
onRejected(this.PromiseResult);
}
}
但是这有一个问题,如果在new Promise的时候使用了定时器,会发生什么?
输出结果
有定时器的then里的东西并没有输出,这是因为定时器setTimeout是宏任务,而我们目前的这个地方then函数还是以同步的方式去执行的,当setTimeout执行,状态改变时,then已经执行完了,then执行的时候promise的状态还是pending,所以then内的回调函数并没有执行。
我们需要一个数据结构存放pending状态的promise,等到promise状态改变了才通知then执行。这样就不会让then在promise还是pending的时候执行。
可以创建两个队列:onfulfilledCallback和onRejectedCallback存放状态为pending的promise
class MyPromise {
static PENDING = "pending";
static FULLFILLED = "fullfilled";
static REJECTED = "rejected";
constructor(fn) {
this.PromiseState = MyPromise.PENDING;
this.PromiseResult = undefined;
try {
/*
这里由于resolve和rejecte函数是传到外部使用的,this会指向外部作用域。
导致resovle和rejecte无法通过this修改promise实例的状态
所以在传出去的时候先要绑定一下this,让它一直指向当前promise实例
*/
const RESOLVE = this.resolve.bind(this);
const REJECT = this.reject.bind(this);
this.onFullfilledCallback = []; //保存成功的回调
this.onRejectedCallback = []; //保存失败的回调
fn(RESOLVE, REJECT);
} catch (err) {
this.reject(err);
}
}
resolve(res) {
if (this.PromiseState !== MyPromise.PENDING) return;
this.PromiseState = MyPromise.FULLFILLED;
this.PromiseResult = res;
while (this.onFullfilledCallback.length) {
this.onFullfilledCallback.shift()(this.PromiseResult);
}
}
reject(res) {
if (this.PromiseState !== MyPromise.PENDING) return;
this.PromiseState = MyPromise.REJECTED;
this.PromiseResult = res;
while (this.onRejectedCallback.length) {
this.onRejectedCallback.shift()(this.PromiseResult);
}
}
then(onFulfilled, onRejected) {
// 先判断传入的值是否为函数,如果不是先包装为函数
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : () => {};
onRejected = typeof onRejected === "function" ? onRejected : () => {};
if (this.PromiseState === MyPromise.FULLFILLED) {
// 如果状态为fulfill就调用onFulfilled函数,并将结果传入
onFulfilled(this.PromiseResult);
} else if (this.PromiseState === MyPromise.REJECTED) {
// 如果状态为fulfill就调用onrejected函数,并将结果传入
onRejected(this.PromiseResult);
} else if (this.PromiseState === MyPromise.PENDING) {
// 将pending的promise装入队列中
this.onFullfilledCallback.push(onFulfilled.bind(this));
this.onRejectedCallback.push(onRejected.bind(this));
}
}
}
等到resovle或者reject调用,即promise状态确定的时候,再取出来进行调用。
链式调用
链式调用就是then之后还能继续调用then。那么哪里有then方法呢?肯定是在promise里。所以说只要then执行后返回一个新的promise就能继续then下去了。
then(onFulfilled, onRejected) {
// 先判断传入的值是否为函数,如果不是先包装为函数
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : () => {};
onRejected = typeof onRejected === "function" ? onRejected : () => {};
const newPromise = new MyPromise(() => {
};
if (this.PromiseState === MyPromise.FULLFILLED) {
// 如果状态为fulfill就调用onFulfilled函数,并将结果传入
onFulfilled(this.PromiseResult);
} else if (this.PromiseState === MyPromise.REJECTED) {
// 如果状态为fulfill就调用onrejected函数,并将结果传入
onRejected(this.PromiseResult);
} else if (this.PromiseState === MyPromise.PENDING) {
// 将pending的promise装入队列中
this.onFullfilledCallback.push(onFulfilled.bind(this));
this.onRejectedCallback.push(onRejected.bind(this));
}
});
return newPromise;
}
返回值处理
在then的回调函数内可以返回值或者抛出错误,我们需要对这些返回值进行处理。
promise对返回值的处理是这样的
- 返回自身-->报错
- 返回一个promise --> 状态由该promise决定
- 返回一个非promise的值 --> 包装为一个fulfilled状态的promise
- 抛出错误 --> promise状态为rejected
const handleReturn = (callback) => {
try {
const returnVal = callback(this.PromiseResult);
if (returnVal === newPromise) {
throw new Error("不能返回自身");
} else if (returnVal instanceof MyPromise) {
returnVal.then(resolve, reject);
} else {
resolve(returnVal);
}
} catch (error) {
reject(error);
throw new Error(error);
}
微任务调用
这里我还没有想出模拟微任务的方法,暂时先使用宏任务模拟微任务。我的想法是在resolve或rejecte方法内添加上定时器,让then内的回调函数的调用变为宏任务。
整体代码
class MyPromise {
static PENDING = "pending";
static FULLFILLED = "fullfilled";
static REJECTED = "rejected";
constructor(fn) {
this.PromiseState = MyPromise.PENDING;
this.PromiseResult = undefined;
this.reutrnVal = null;
try {
/*
这里由于resolve和rejecte函数是传到外部使用的,this会指向外部作用域。
导致resovle和rejecte无法通过this修改promise实例的状态
所以在传出去的时候先要绑定一下this,让它一直指向当前promise实例
*/
const RESOLVE = this.resolve.bind(this);
const REJECT = this.reject.bind(this);
this.onFullfilledCallback = []; //保存成功的回调
this.onRejectedCallback = []; //保存失败的回调
fn(RESOLVE, REJECT);
} catch (err) {
this.reject(err);
}
}
resolve(res) {
setTimeout(() => {
if (this.PromiseState !== MyPromise.PENDING) return;
this.PromiseState = MyPromise.FULLFILLED;
this.PromiseResult = res;
while (this.onFullfilledCallback.length) {
this.onFullfilledCallback.shift()();
}
});
}
reject(res) {
setTimeout(() => {
if (this.PromiseState !== MyPromise.PENDING) return;
this.PromiseState = MyPromise.REJECTED;
this.PromiseResult = res;
while (this.onRejectedCallback.length) {
this.onRejectedCallback.shift()();
}
});
}
then(onFulfilled, onRejected) {
// 先判断传入的值是否为函数,如果不是先包装为函数
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : () => {};
onRejected = typeof onRejected === "function" ? onRejected : () => {};
const newPromise = new MyPromise((resolve, reject) => {
const handleReturn = (callback) => {
try {
const returnVal = callback(this.PromiseResult);
if (returnVal === newPromise) {
throw new Error("不能返回自身");
} else if (returnVal instanceof MyPromise) {
returnVal.then(resolve, reject);
} else {
resolve(returnVal);
}
} catch (error) {
reject(error);
throw new Error(error);
}
};
if (this.PromiseState === MyPromise.FULLFILLED) {
// 如果状态为fulfill就调用onFulfilled函数,并将结果传入
onFulfilled(this.PromiseResult);
} else if (this.PromiseState === MyPromise.REJECTED) {
// 如果状态为fulfill就调用onrejected函数,并将结果传入
onRejected(this.PromiseResult);
} else if (this.PromiseState === MyPromise.PENDING) {
// 将pending的promise装入队列中
this.onFullfilledCallback.push(handleReturn.bind(this, onFulfilled));
this.onRejectedCallback.push(handleReturn.bind(this, onRejected));
}
});
return newPromise;
}
}