-
面试时经常会碰到手写Promise的面试题,在此记录一下
-
整体思路
-
- Promise 是一个类,在执行这个类的时候,需要传递一个执行器进去,执行器就会立即执行;
-
- Promise 有三种状态, 成功 - Fullfilled; 失败 - Rejected; 等待 - Pending;并且promise最终只会出行两种结果, Pending -> Fullfilled; Pending -> Rejected;而且promise的状态一旦确定不可更改;
-
- resolve 和 rejected 是用来更改状态的, 需要两个相对应的方法;
-
- then 方法内部做的最主要的事情就是判断状态。 成功走成功的回调,失败走失败的回调。
-
- 如果 then 中有异步操作,那么状态就会为Pending,所以需要把回调存起来; 在resolve 和 reject的时候去检测, 是否存在成功失败的回调,存在就调用并且把值传进去。
-
- 如果多次调用then, 并且包含异步操作, 我们需要把成功 / 失败 的回调存储起来放到一个数组中, 在resolve / reject 时利用 while 循环调用, 判断数组的 length 是否存在, 存在调用shift()方法, 把第一个拿出来, 传入相应的值。 MyPromise.js
-
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class MyPromise {
constructor (callback) {
// 利用try catch 来捕获错误,如果报错了就直接调用reject把错误反出去
try {
callback(this.resolve, this.reject);
} catch (err) {
this.reject(err)
}
}
status = PENDING; // 状态默认是pending;
// 保存值
successVal = undefined;
failVal = undefined;
// 保存成功失败的函数
successCallback = [];
failCallback = [];
/**
* 这里resolve 和 reject 使用箭头函数是为了this指向上下文MyPromise,
* function也可以,但是需要改变this指向
*/
resolve = (successVal) => {
// resolve和reject只能从pending -> resolve, pending -> reject
if (this.status !== PENDING) return;
this.status = FULFILLED;
this.successVal = successVal;
while(this.successCallback.length) this.successCallback.shift()();
}
reject = (failVal) => {
if (this.status !== PENDING) return;
this.status = REJECTED;
this.failVal = failVal;
while(this.successCallback.length) this.successCallback.shift()();
}
then (successCallback, failCallback) {
/**
* .then 方法是可以链式调用的,比如 .then().then().then(val => console.log(val)),
* 这个时候就需要把没有做操作的then中的值return给下一个then
*/
successCallback = successCallback ? successCallback : successVal => successVal;
failCallback = failCallback ? failCallback : failVal => {throw failVal};
// 由于promise的 .then 方法返回一个promise,所以这里也返回一个mypromise;
let promiseBack = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
// 因为调用checkPromise的时候拿不到promiseBack, 所以调用seTTimeout 把代码变成异步的
setTimeout(() => {
try {
let result = successCallback(this.successVal);
checkPromise(promiseBack, result, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
} else if (this.status === REJECTED) {
setTimeout(() => {
try {
let result = failCallback(this.failVal);
checkPromise(promiseBack, result, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
} else {
// pending状态
/**
* 当then被多次调用同时还携带异步操作的时候,就需要把成功和失败的函数存储起来
* 在resolve和reject的时候循环调用
*/
this.successCallback.push(() => {
setTimeout(() => {
try {
let result = successCallback(this.successVal);
checkPromise(promiseBack, result, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
this.failCallback.push(() => {
setTimeout(() => {
try {
let result = failCallback(this.failVal);
checkPromise(promiseBack, result, resolve, reject);
} catch (e) {
reject(e);
}
}, 0)
});
}
});
return promiseBack;
}
/**
* catch 是可以在then后边链式调用的并且返回一个promise, then自己也可以链式调用并且返回一个promise
* 这里借助then直接调用,返回rejecct
*/
catch(failCallback) {
return this.then(undefined, failCallback);
}
// finally 不管对错都会给一个结果,而且后面可以链式调用then, 所以返回一个promise
finally(finallyCallback) {
return this.then(val => {
return MyPromise.resolve(finallyCallback()).then(() => val);
}, err => {
return MyPromise.resolve(finallyCallback()).then(() => {throw err});
})
}
// all 是需要promise.all调用的 所以这里需要写在static上
static all(arr) {
return new MyPromise((resolve, reject) => {
let result = []; // 用来存放all传进来的promise或普通值;
let index = 0; //
for (let i = 0; i < arr.length; i++) {
let current = arr[i];
// 这里需要判断all方中传进来的每一个值是普通值还是promise
if (current instanceof MyPromise) {
/**
* 1、是promise就需要拿到值,并把值存进去,如果在拿值的过程中报错了就直接报错;
* 2、all方法的特点是全部返回才会返回,有一个没返回就挂掉,所以用index来计数
*/
current.then(val => add(i, val), err => reject(err));
} else {
add(i, current);
}
}
/**
* 一个辅助函数,因为被多次运用到,抽离出来
* 用来存放all方法中传进来的值
*/
function add (key, value) {
result[key] = value;
index++; // 没存一个index就++
if (arr.length === index) {
// 如果都返回了,就resolve出去
resolve(result);
}
}
})
}
// resolve 是需要promise.resolve调用的 所以这里需要写在static上
static resolve(val) {
/**
* resolve就是返回一个成功的promise,所以需要判断val是不是一个promise
* 是 直接return
* 否 创建一个promise并且把值resolve出去
*/
if (val instanceof MyPromise) {
return val;
}
return new MyPromise((resolve, reject) => resolve(val));
}
}
/**
* 1.一个辅助函数,有来判断then中返回的是不是一个promise,如果是就调用.then方法拿到值,然后resolve出去
* 如果不是,就直接resolve出去
* 2.判断是否出现了promise循环调用的情况,如果是就报错循环调用了
*/
function checkPromise(promise, result, resolve, reject) {
// 判断回调的返回是不是和当前then的返回是一样的promise,如果是就是循环调用了
if (promise === result) {
return reject(new TypeError('promise 被循环调用了'));
}
// 判断回调的返回是不是一个promise,如果是就then到它的值然后传出去
if (result instanceof MyPromise) {
result.then(resolve, reject);
} else {
resolve(result);
}
}