为什么还要实现promise???,因为现有的手写方案,有的不符合MDN的描述、有的不符合promise/A+规范、有的不能兼容同步和异步两种内部函数的使用形式。故此,还是自己动手、丰衣足食,实现了PromiseDemo,以下是测试用例及实现源码。
以下测试用例均已通过:
// 用例1:同步情况下resolve
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:异步resolve
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
});
// 用例7: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
});
PromiseDemo实现源码
感兴趣的同学,建议观码顺序:
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;
}
}
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!