为什么还要实现promise???,因为现有的手写方案,有的不符合MDN的描述、有的不符合promise/A+规范、有的不能兼容同步和异步两种内部函数的使用形式。故此,还是自己动手、丰衣足食。以下是测试用例及实现源码
以下测试用例均已通过:
用例1:测试参数内为同步逻辑时,是否可用.
new PromiseDemo(function(resolve, reject){
resolve('同步')
})
.then(function(res) {
console.log(res); // 同步
});
用例2:测试resolve参数为promise,这种特殊情况时是否可用.
new PromiseDemo(function(resolve, reject){
resolve(new PromiseDemo(function(reso) {
reso('resolve-promose')
}))
})
.then(function(res) {
console.log(res); // resolve-promose
});
用例3:测试错误捕捉.
new PromiseDemo(function(resolve, reject){
throw new Error('错误');
resolve(123);
})
.then(function(res) {
console.log(res);
}).catch(function(err) {
console.log(err);
});
// Error: 错误
// at <anonymous>:2:9
// at new PromiseDemo (<anonymous>:9:13)
// at <anonymous>:1:1
用例4:测试参数内为异步逻辑时,是否可用.
new PromiseDemo(function(resolve, rej) {
setTimeout(function() {
resolve(1);
}, 10)
})
.then(function(res) {
console.log(res); // 1
}, function(err) {
console.log(err);
});
用例5:测试all是否可用
PromiseDemo.all([
fetch('https://cdn.bootcss.com/vue/2.5.16/vue.min.js',{method: 'GET'}).then((r) => r.text()).then((r) => r.slice(6,21)),
fetch('https://unpkg.com/react@16/umd/react.production.min.js', {method: 'GET'}).then((r) => r.text()).then((r) => r.slice(13,27)),]
)
.then(function(res) {
console.log(res); // [" Vue.js v2.5.16", "React v16.14.0"]
});
用例6:测试race是否正确执行
PromiseDemo.race([
fetch('https://cdn.bootcss.com/vue/2.5.16/vue.min.js', {method: 'GET'}).then((r) => r.text()).then((r) => r.slice(6,21)),
fetch('https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js', {method: 'GET'}).then((t) => t.text()).then((r) => r.slice(3,17))]).then(function(res) { console.log(res); // Vue.js v2.5.16});
同用例6:换个请求资源,测试race是否正确执行
PromiseDemo.race([
fetch('https://unpkg.com/react@16/umd/react.production.min.js', {method: 'GET'}).then((r) => r.text()).then((r) => r.slice(13,27)),
fetch('https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js', {method: 'GET'}).then((t) => t.text()).then((r) => r.slice(3,17))]
)
.then(function(res) {
console.log(res); // jQuery v3.4.1
});
实现源码
感兴趣的同学,建议观码顺序如下:
1.constructor
2.resolve
3.then
4.reject
5.catch
6.finally
7.all
8.race
根据MDN描述,resolve、reject、all、race为静态方法,所以写了static
根据Promise/A+规范,resolve的参数可能是promise,故在resolve内部还要判断是否是PromiseDemo的实例,再进行逻辑拆分。
class PromiseDemo { /** 构造函数 * 初始化then事件队列、catch事件队列、状态status * 执行参数fn,传入resolve和reject */ constructor(fn) { if (fn && typeof fn !== 'function') throw new Error(Parameter is not a function); this.thenQue = [] this.catchQue = [] this.status = '<pending>'; try { fn && fn(PromiseDemo.resolve.bind(this), PromiseDemo.reject.bind(this)); } catch(e) { PromiseDemo.reject.bind(this)(e); } } /** resolve函数 * 主要作用为执行thenQue队列中的函数 * 如果状态为fulfilled,则不再执行 * 如果resolve的参数不是PromiseDemo实例,则正常执行,这也是我们常用的场景 * 如果resolve的参数是PromiseDemo实例,则将实例内部的resolve值或者then内的函数返回值暴露出来,连接上外部的resolve执行,这样可以接着用外部的then函数队列,依次执行。 * 如果resolve一开始调用时,没有值,则返回一个PromiseDemo实例,因为存在用法Promise.resolve().then() * 函数内使用了setTimeout,是为了将后续逻辑加入下一个宏任务,此时,then将优先执行,提前将函数逻辑加入thenQue队列 * 注意,promise是微任务,此处用setTimeout是为了实现promise效果,因为浏览器环境下,除了mutation和promise外,没有可以异步的函数了。 */ static resolve(data) { if (this.status === '<fulfilled>') return; this.value = data; const isInstance = data instanceof PromiseDemo; if (!isInstance) { setTimeout(() => { try { this.thenQue.forEach((item) => { this.value = item(this.value); }); this.thenQue = []; this.status = '<fulfilled>'; } catch (e) { this.status = '<rejected>'; PromiseDemo.reject.bind(this)(e); } }); } else { data.then((res) => { PromiseDemo.resolve.bind(this)(res); }).catch((err) => { PromiseDemo.reject.bind(this)(err); }); } if (!data) { return new PromiseDemo(); } } /** reject函数 * 主要作用是执行catchQue事件队列中的catch事件函数 */ static reject(err) { if (this.status === '<rejected>') return; this.error = err; let count; setTimeout(() => { try { this.catchQue.forEach((item, index) => { count = index; this.error = item(this.error); }); this.catchQue = []; this.status = '<rejected>'; } catch (e) { this.catchQue = this.catchQue.slice(count+1); PromiseDemo.reject.bind(this)(e); } }); if (!err) { return new PromiseDemo(); } } /** then函数 * 主要作用为将then内的函数全部收集起来,组成then事件队列thenQue */ then(onResolve, onReject) { if (typeof onReject === 'function') { this.catchQue.push(onReject); } typeof onResolve === 'function' && this.thenQue.push(onResolve); return this; } /** catch函数 * 主要作用为将catch内的函数全部收集起来,组成catch事件队列catchQue */ catch(fn) { this.catchQue.push(fn); return this; } /** finally函数 * 将fn推入队列,无论事件队列thenQue执行,还是catchQue执行,最后都可以执行到 */ finally(fn) { this.thenQue.push(fn); this.catchQue.push(fn); } /** all函数 * 参数为数组,数组每一项都是PromiseDemo的实例 * 对每项添加then方法,则当执行到then内部方法时,判断是否全部promise都已执行完,若都已执行完毕,则整体resolve */ static all(arr) { const resArr = []; const length = arr.length; let resCount = 0; let that; try { arr.forEach(function(item, index) { item.then(function(res) { resArr[index] = res; resCount++; if (resCount === length) { PromiseDemo.resolve.bind(that)(resArr); } }) }); } catch (e) { PromiseDemo.reject.bind(that)(e); } that = new PromiseDemo(); return that; } /** race函数 * 只要有一个执行完毕,则直接整体resolve,当另一个也执行到resolve时,因status已发生改变,则不会再向下执行 */ static race(arr) { let that; try { arr.forEach(function(item, index) { item.then(function(res) { PromiseDemo.resolve.bind(that)(res); }) }); } catch (e) { PromiseDemo.reject.bind(that)(e); } that = new PromiseDemo(); return that; }}