继上一次重写Promise之后,已有两年了。由于个人原因,毕业之后从前端转向了后端,但按捺不住前端之心,于是最近几天重新开始学了一下,目前学到了异步处理这一块,重写Promise,算是回顾自己,加深对Promise的理解。
什么是Promise?
我们都知道Promise是ES6推出的一个官方标准,其实在官方推出之前,社区就已经有Promise,并且形成了一些规范,这些规范称为Promise A规范(后拓展到Promise A+规范)。译文如下:
另外我们也应该已经知道了,宏任务和微任务的一些相关知识。我大胆的总结一下:
宏任务(或称宏队列):在ES6推出之前所有的异步操作都是宏任务,包括网络、事件监听、计时等
微任务(或称微队列):在ES6推出之后的异步操作都属于微任务,包括Promise、MutationObserver(H5的一系列Observer)
这里不得不重新提一下JS引擎执行的机制,由于JS引擎单线程的原因,前端省去了多任务操作时共享数据、死锁等一系列问题,但也引入了一些其他麻烦,诸如大数据量耗时操作的同步处理问题,在以前我们都是丢到setTimeout里,让计时线程去做这个事,引起这一操作的原理可以大致总结如下:
JS执行时会向执行栈推入一些数据,这些数据里面存储了每一个函数执行时的上下文(诸如AO、GO)
JS会首先将执行栈里面的执行完,执行一个移除一个(栈是先进后出,所有一个函数藏得越深被执行时最先退出执行栈)
执行栈清空之后,浏览器会在内部开一个线程定期去看任务队列里面有没有任务,如果有就会将任务队列里的第一个拿出来到执行栈去执行,直到把任务队列也清空
任务队列有两个,宏任务队列和微任务队列,微任务队列里的任务优先级比宏任务队列里的任务优先级高,所以都是将微任务队列里的任务清空之后再去清空宏任务队列里的任务
开始编写Promise
编码之前,读者需将Promise的执行例子多写几个,了解一下Promise如何执行的,可更有效
1. 状态改变
Promise A+规范,规定Promise有3个状态,分别是pending、fulfilled、rejected,这三个状态只能由pending=>fulfilled或pending=>rejected,状态转换不可逆,且只能执行一次,多次执行只会取第一次状态改变的那个结果。
const PENDING = 'pending';
const REJECTED = 'rejected';
const RESOLVED = 'resolved';
class MyPromise {
_changeState = (newState, data) => {
if (this._state !== PENDING) return;
this._state = newState;
this.value = data;
}
_reject = reason => {
this._changeState(REJECTED, reason);
}
_resolve = value => {
this._changeState(RESOLVED, value);
}
constructor(excutor) {
this._state = PENDING;
this._value = undefined;
try {
excutor(this._resolve, this._reject);
}
catch (err) {
this._reject(err);
}
}
}
2. 模拟微任务队列
我们都知道微任务是要先于宏任务执行的,所以我们这里不能简单地使用setTimeout进行模拟,这里有两种方法来模拟,一个是Node环境一个是浏览器环境下:
Node环境我们一般用:process.nextTick,在下一个tick回调函数会被扔到微队列
浏览器环境我们使用:MutationObserver,监听元素内部发生变化是,会将回调函数扔到微队列
其他情况没办法只能使用setTimeout
function runMicroTask(cb) {
if (process && process.nextTick) {
process.nextTick(cb);
}
else if (MutationObserver) {
// MutationObserver在监听元素内部变化时,会将回调函数放到微队列,很适合我们模拟微任务队列
const p = document.createElement('p');
const observer = new MutationObserver(cb);
observer.observe(p,
{
childNodes: true,
});
p.innerHTML = "1";
}
else {
setTimeout(cb, 0);
}
}
3. 重头戏开始,编写then
Promise的then函数都有一个特点,即不论你怎么写代码,它都返回一个Promise,啥也不说,咱们上来就是一个返回Promise:
class MyPromise {
constructor(executor) {
this._state = PENDING;
this._value = undefined;
+ this._handlers = [];
try {
excutor(this._resolve, this._reject)
} catch (err) {
this._reject(err);
};
}
_pushHandler = (executor, state, resolve, reject) => {
this.handlers.push({ executor, state, resolve, reject });
}
then = (onFulfilled, onRejected) => {
return new MyPromise((resolve, reject) => {
this._pushHandler(onFulfilled, RESOLVED, resolve, reject);
this._pushHandler(onRejected, REJECTED, resolve, reject);
this._runHandlers();
});
}
}
做一下解释:
由于Promise的then方法里的函数并不是立即执行,而是需要上一个Promise决议之后才会执行,所以这里需要保存当前执行的方法还有对应决议时调用的resolve或reject方法,来改变当前Promise的状态,而调用resolve或reject方法与onFulfilled或onRejected方法有关,所以也需要保存当前方法执行时所处的状态
所以then函数并不会立即执行回调函数,想要执行回调函数还得等到上一个Promise状态改变,所以我们需要在状态改变的时候执行将执行队列里的每一个函数分状态情况执行
3. 执行执行队列
执行执行队列时,我们需要搞清楚在什么时候执行,即状态变更的时候进行执行,但状态变更有两种情况。
立即变更:立即变更时,我们需要马上调用then函数,将then里的回调函数扔到微任务,去执行微任务
new Promise((resolve, reject) => {
resolve(1)
}).then(data => {
console.log(data);
})
延迟变更:延迟变更时,由于状态没有发生改变,调用then方法时,不会立即将回调函数扔到微任务队列去,例如
new Promise((resolve, reject) => {
setTimout(() => {
resolve(123);
}, 500);
}).then(data => {
console.log(data); // 500ms之后才会输出
})
所以执行执行队列时,有两个地方执行他,分别是then、changeState
这里还需要各位根据Promise的规则去尝试一下,各种返回情况下是怎么个结果
代码如下:
function isThenable(obj) {
return !!(obj && typeof obj === 'object' && typeof obj.then === 'function' && obj.then.length === 2);
}
class MyPromise {
_changeState = (newState, value) => {
if (this._state !== PENDING) return;
this._state = newState;
this._value = value;
+ this._runHandlers();
}
_runHandlers = () => {
if (this._state === PENDING) return;
while (this._handlers[0]) {
this._runHandler(this._handlers[0]);
this._handlers.shift();
}
}
_runHandler = ({ executor, reject, resolve, state }) => {
runMicroTask(() => {
if (state !== this._state) {
return;
}
// 传递的执行函数不是一个函数那么返回的Promise应该具有和当前Promise一样的状态和数据
if (typeof executor !== 'function') {
this._state === REJECTED ?
reject(this._value) :
resolve(this._value);
return;
}
try {
const result = executor(this._value);
// 是否含有then方法
if (isThenable(result)) {
result.then(resolve, reject);
}
else {
resolve(result);
}
} catch (err) {
reject(err);
}
});
}
}
重难点解析:
关于thenable:Promise A+规范并没有规定说Promise必须是某某对象,而是指具有thenable方法的鸭子类型的对象,所以在上古时期,就算每个厂家的Promise不同,但却能够很好的相互结合工作,是因为他们都基于Promise A+规范实现的。所以一个对象只要是thenable,就可以将结果进行执行输出。
Promise执行时,如果传入的参数不是一个函数,那么就相当于跳过,所以他返回的这个Promise就应具有上一个Promise的状态和值等信息
好了,Promise A+ 规范的Promise最基本的已经完成。
注意:实现了Promise A+规范的Promise可以和Promise混用,而且支持async await等新语法
这一部分代码如下:
const PENDING = 'pending';
const REJECTED = 'rejected';
const RESOLVED = 'resolved';
/**
* 把传递的函数放到微队列里(模拟)
* @param {Function}} cb 回调函数
*/
function runMicroTask(cb) {
if (process && process.nextTick) {
process.nextTick(cb);
}
else if (MutationObserver) {
// MutationObserver在监听元素内部变化时,会将回调函数放到微队列
const p = document.createElement('p');
const observer = new MutationObserver(cb);
observer.observe(p,
{
childNodes: true,
});
p.innerHTML = "1";
}
else {
setTimeout(cb, 0);
}
}
function isThenable(obj) {
return !!(obj && typeof obj === 'object' && typeof obj.then === 'function' && obj.then.length === 2);
}
class MyPromise {
then = (onFulfilled, onRejected) => {
return new MyPromise((resolve, reject) => {
this._pushHandler(onFulfilled, RESOLVED, resolve, reject);
this._pushHandler(onRejected, REJECTED, resolve, reject);
this._runHandlers();
});
}
_pushHandler = (executor, state, resolve, reject) => {
this._handlers.push({ executor, state, resolve, reject });
}
_runHandlers = () => {
if (this._state === PENDING) return;
while (this._handlers[0]) {
this._runHandler(this._handlers[0]);
this._handlers.shift();
}
}
_runHandler = ({ executor, reject, resolve, state }) => {
runMicroTask(() => {
if (state !== this._state) {
return;
}
// 传递的执行函数不是一个函数那么返回的Promise应该具有和当前Promise一样的状态和数据
if (typeof executor !== 'function') {
this._state === REJECTED ?
reject(this._value) :
resolve(this._value);
return;
}
try {
const result = executor(this._value);
// 是否含有then方法
if (isThenable(result)) {
result.then(resolve, reject);
}
else {
resolve(result);
}
} catch (err) {
reject(err);
}
});
}
_changeState = (newState, value) => {
if (this._state !== PENDING) return;
this._state = newState;
this._value = value;
this._runHandlers(); // 执行队列
}
_resolve = value => {
this._changeState(RESOLVED, value);
}
_reject = reason => {
this._changeState(REJECTED, reason);
}
constructor(excutor) {
this._state = PENDING;
this._value = undefined;
this._handlers = [];
try {
excutor(this._resolve, this._reject)
} catch (err) {
this._reject(err);
};
}
}
接下来,稍微测试一下:
/*****************测试1 start********************/
console.log('start');
setTimeout(() => {
console.log('setTimeout1');
}, 0);
const pro = new MyPromise((resolve, reject) => {
console.log('promise1');
setTimeout(() => {
console.log('setTimeout2');
resolve(1);
}, 100);
});
pro.then(data => {
console.log('promise2');
return new MyPromise((resolve, reject) => {
resolve(data);
})
}).then(data => {
console.log(data);
});
console.log('end');
/*****************测试1 end********************/
/*****************语法测试 start********************/
(async () => {
const n = await new MyPromise((resolve, reject) => {
resolve(1);
});
console.log(n);
})();
/*****************语法测试 end********************/
测试结果如下:
4. 其他实例方法catch、finally
then完成了,catch和finally就异常简单,只不过需要注意finally。执行finally时,是没有参数的,且如果执行过程没报错的话,返回的Promise是没有value的,如果执行报错的话,返回的Promise中value就是报错的内容。
class MyPromise {
catch = onRejected => {
return this.then(null, onRejected);
}
finally = onFinally => {
let fn = data => onFinally(data);
// 这里给包一层,避免将onFinally传递下去时含有参数。在reject里手动抛个错,让then捕获
return this.then(
data => {
onFinally();
return data;
},
reason => {
onFinally();
throw reason;
});
}
}
接下来就是一些静态方法的编码了,这一部分,需要读者自己去了解Promise的静态方法执行规则,我就不再这里讲解用法了。
class MyPromise {
static race = iterators => {
return new MyPromise((resolve, reject) => {
for (const iterator of iterators) {
// 当前Promise的状态只能变一次,所以这里我简写一下
Promise.resolve(iterator).then(resolve, reject);
}
});
}
static allSettled = iterators => {
const ps = [];
for (const iterator of iterators) {
// 这里有个技巧,将每一个promise的then先写好,避免后续报错影响
ps.push(MyPromise.resolve(iterator).then(value => ({ status: RESOLVED, value }), reason => ({ status: REJECTED, reason })));
}
return MyPromise.all(ps);
}
static all = (iterators) => {
return new MyPromise((resolve, reject) => {
try {
let count = 0;
let reolvedCount = 0;
const results = [];
for (const iterator of iterators) {
let i = count; // 确保当前这一次的i和异步的i是一样的
MyPromise.resolve(iterator).then(data => {
reolvedCount++;
results[i] = data;
// 当前是最后一个promise,则全部完成
if (reolvedCount == count) {
resolve(results);
}
}, reject);
count++;
}
// MDN上规定如果all方法的参数为空数组就直接返回空数组
if (!iterators.length) {
resolve(results);
}
} catch (err) {
reject(err);
}
})
}
static resolve(obj) {
if (obj instanceof MyPromise) {
return obj;
}
return new MyPromise((resolve, reject) => {
if (isThenable(obj)) {
obj.then(resolve, reject);
}
else {
resolve(obj);
}
});
}
static reject(reason) {
return new MyPromise((resolve, reject) => {
reject(reason);
});
}
}
源代码如下:
const PENDING = 'pending';
const REJECTED = 'rejected';
const RESOLVED = 'resolved';
function runMicroTask(cb) {
if (process && process.nextTick) {
process.nextTick(cb);
}
else if (MutationObserver) {
// MutationObserver在监听元素内部变化时,会将回调函数放到微队列
const p = document.createElement('p');
const observer = new MutationObserver(cb);
observer.observe(p,
{
childNodes: true,
});
p.innerHTML = "1";
}
else {
setTimeout(cb, 0);
}
}
function isThenable(obj) {
return !!(obj && typeof obj === 'object' && typeof obj.then === 'function' && obj.then.length === 2);
}
class MyPromise {
static race = iterators => {
return new MyPromise((resolve, reject) => {
for (const iterator of iterators) {
Promise.resolve(iterator).then(resolve, reject);
}
});
}
static allSettled = iterators => {
const ps = [];
for (const iterator of iterators) {
ps.push(MyPromise.resolve(iterator).then(value => ({ status: RESOLVED, value }), reason => ({ status: REJECTED, reason })));
}
return MyPromise.all(ps);
}
static all = (iterators) => {
return new MyPromise((resolve, reject) => {
try {
let count = 0;
let reolvedCount = 0;
const results = [];
for (const iterator of iterators) {
let i = count; // 确保当前这一次的i和异步的i是一样的
MyPromise.resolve(iterator).then(data => {
reolvedCount++;
results[i] = data;
// 当前是最后一个promise,则全部完成
if (reolvedCount == count) {
resolve(results);
}
}, reject);
count++;
}
if (!iterators.length) {
resolve(results);
}
} catch (err) {
reject(err);
}
})
}
static resolve(obj) {
if (obj instanceof MyPromise) {
return obj;
}
return new MyPromise((resolve, reject) => {
if (isThenable(obj)) {
obj.then(resolve, reject);
}
else {
resolve(obj);
}
});
}
static reject(reason) {
return new MyPromise((resolve, reject) => {
reject(reason);
});
}
then = (onFulfilled, onRejected) => {
return new MyPromise((resolve, reject) => {
this._pushHandler(onFulfilled, RESOLVED, resolve, reject);
this._pushHandler(onRejected, REJECTED, resolve, reject);
this._runHandlers(); // 执行队列
});
}
catch = onRejected => {
return this.then(null, onRejected);
}
finally = (onFinally) => {
let fn = data => onFinally(data);
return this.then(
data => {
onFinally();
return data;
},
reason => {
onFinally();
throw reason;
});
}
_pushHandler = (executor, state, resolve, reject) => {
this._handlers.push({ executor, state, resolve, reject });
}
_runHandlers = () => {
if (this._state === PENDING) return;
while (this._handlers[0]) {
this._runHandler(this._handlers[0]);
this._handlers.shift();
}
}
_runHandler = ({ executor, reject, resolve, state }) => {
runMicroTask(() => {
if (state !== this._state) {
return;
}
// 传递的执行函数不是一个函数那么返回的Promise应该具有和当前Promise一样的状态和数据
if (typeof executor !== 'function') {
this._state === REJECTED ?
reject(this._value) :
resolve(this._value);
return;
}
try {
const result = executor(this._value);
// 是否含有then方法
if (isThenable(result)) {
result.then(resolve, reject);
}
else {
resolve(result);
}
} catch (err) {
reject(err);
}
});
}
_changeState = (newState, value) => {
if (this._state !== PENDING) return;
this._state = newState;
this._value = value;
this._runHandlers(); // 执行队列
}
_resolve = value => {
this._changeState(RESOLVED, value);
}
_reject = reason => {
this._changeState(REJECTED, reason);
}
constructor(excutor) {
this._state = PENDING;
this._value = undefined;
this._handlers = [];
try {
excutor(this._resolve, this._reject)
} catch (err) {
this._reject(err);
};
}
}