如果要实现Promise/A+规范 还是比较复杂的。这里仅仅只是写个基本实现,为了一步步的探讨Promise的所有功能,计划分几次来逐步完善。
先看看promise的一个简单使用
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success!');
}, 1000);
});
p.then((res) => {
console.log(`Get async messge: ${res}`);
})
如果仅仅只是实现上面的功能,对于有经验程序员来说,观察者模式中注册事件然后触发事件调用回调函数,可以很快的得到思路。无非就是
在p.then(fn)中将函数注册到监听函数队列中,然后emit这个事件的时候去执行队列中的事件函数。
下面就这种最简单的情况写一个基本实现;(对于nextTick,仅仅只是上面使用可以不使用,或者使用一个简单的 setTimeout完事;)
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class Promise1 {
constructor(fn) {
this.status = PENDING;
this.value = undefined;
this.sucCallBack = [];
this.failCallBack = [];
fn(this.resolve.bind(this), this.reject.bind(this));
}
// resolve, reject 都是触发回调的真正时机
// 如果将其换个名词'emit' 相信很多同学能秒懂 当然这里都只是简单实现
resolve (res) {
// 状态只能由pending=>resolved
// 如果是不是pending 就应该报错这里直接返回吧;
if (this.status !== PENDING) { return; }
this.status = FULFILLED;
this.nextTick(() => {
this.value = res;
this.sucCallBack.forEach(fn => fn(this.value));
});
}
reject(err) {
if (this.status !== PENDING) { return; }
this.status = REJECTED;
// 如果不想使nextTick 直接传入的箭头函数 对于上面的使用是没问题的
this.nextTick(() => {
this.value = err;
this.failCallBack.forEach(fn => fn(this.value));
});
}
// 调用then就类似于 观察者模式中的回调栈中推入待执行的回调函数;
then(suc, fail) {
this.sucCallBack.push(() => suc(this.value));
this.failCallBack.push(() => fail(this.value));
}
nextTick(fn) {
if (typeof fn !== 'function') { throw new Error('must pass funciton');}
// 在nodejs中process上本身就有 nextTick的方法 不过为了练习 还是想手动撸一撸这段代码
// 关于nextTick的实现 在vue源码中 大概的实现是使用 promise/MutationObserver/setImmediate/setTimeout
// 上面的四种方式是按着性能来排的也就是实现方式是 promise > MutationObserver > setImmediate > setTimeout
// 由于 这里是promise的实现所以直接跳过promise
// 这个判断MutationObserver兼容的方式还是有点问题的
// 但这里的重点不在此处所以从简
if (typeof MutationObserver !== 'undefined') {
let counter = 0;
const observer = new MutationObserver(fn);
const textNode = document.createTextNode(String(counter));
observer.observe(textNode, {
characterData: true
});
counter = (counter + 1) % 2;
textNode.data = String(counter);
} else if (typeof setImmediate !== undefined) {
setImmediate(fn);
} else {
setTimeout(fn);
}
}
}
console.log('script start');
const p1 = new Promise1((resolve, reject) => {
setTimeout(() => {
resolve('success!');
}, 2000);
});
const p2 = new Promise1((resolve, reject) => {
setTimeout(() => {
reject('fail');
}, 1000);
});
p1.then((res) => {
console.log(`Get async message: ${res}`);
});
p2.then((res) => {
console.log(`Get async message: ${res}`);
}, (err) => {
// 执行该处
console.log(`Get a fail message: ${err}`);
});
上面的代码跑起来还真像那么回事,之前的回调函数能铺平了 放在then中,不过promise最初想要解决的问题是解决回调地狱。也就是说如果有多个异步函数的回调嵌套了依然要能铺平,再看看上面的代码实现明显臣妾做不到呀!对,下面就来实现then的链式调用;
1.要链式调用 自然就相对在then中要返回一个 promise, 这里先只考虑then中的回调函数不返回promise的情况。
// 仅仅只修改下 then函数,其余的代码一致
// 调用then就类似于 观察者模式中的回调栈中推入待执行的回调函数;
// then的链式调用实现,先来个最简实现
// 后面then中的回调函数应该能接收到
then(suc, fail) {
return new Promise1((resolve, reject) => {
this.nextTick(() => {
this.sucCallBack.push((res) => {
let result = suc(res);
resolve(result);
});
this.failCallBack.push((error) => {
let result = fail(error);
resolve(result);
});
})
})
}
// 测试代码
p1.then((res) => {
console.log(`Get async message: ${res}`);
return 'Link';
}).then((res) => {
console.log(`From suc Get async message second: ${res}`);
});
p2.then((res) => {
console.log(`Get async message: ${res}`);
}, (err) => {
console.log(`Get a fail message: ${err}`);
return 'Link'
}).then((res) => {
console.log(`From fail Get message second: ${res}`);
});
写到这里一个简单的promise就基本完成了,后续会继续更新一步步将其完善。