依据标准自定的Promise工具类,便于学习理解Promise,也可尝试用于项目使用,百分百通过了 Promise/A+ 的标准测试。
如果你喜欢,烦请给个 star
,谢谢!
详细实现过程,请阅读我的分享 深入理解 Promise (中)
- all
- race
- stop
- defer
- resolve
- reject
- sequence
- wait
- always
- done
- timeout
用于并行执行promise组成的数组(数组中可以不是Promise对象,在调用过程中会使用 Promise.resolve(value)
转换成Promise对象),如果全部成功则获得成功的结果组成的数组对象,如果失败,则获得失败的信息,返回一个新的Promise对象
Promise.all = function(iterable){
var _this = this;
return new this(function(resolve, reject){
if(!iterable || !Array.isArray(iterable)) return reject( new TypeError('must be an array') );
var len = iterable.length;
if(!len) return resolve([]);
var res = Array(len), called=false;
iterable.forEach(function(v, i){
(function(i){
_this.resolve(v).then(function(value){
res[i]=value;
if(++counter===len && !called){
called = true;
return resolve(res)
}
}, function(err){
if(!called){
called = true;
return reject(err);
}
})
})(i)
})
})
}
使用方式
function fn1(){
return new Promise(resolve => setTimeout(()=>resolve(1), 3000))
}
function fn2(){
return new Promise(resolve => setTimeout(()=>resolve(2), 2000))
}
Promise.all([fn1(), fn2()]).then(res=>console.log(res), err=>console.log(err))
// [1, 2]
用于并行执行promise组成的数组(数组中可以不是Promise对象,在调用过程中会使用 Promise.resolve(value)
转换成Promise对象),如果某个promise的状态率先改变,就获得改变的结果,返回一个新的Promise对象
Promise.race = function(iterable){
var _this = this;
return new this(function(resolve, reject){
if(!iterable || !Array.isArray(iterable)) return reject( new TypeError('must be an array') );
var len = iterable.length;
if(!len) return resolve([]);
var called = false;
iterable.forEach(function(v, i){
_this.resolve(v).then(function(res){
if(!called){
called = true;
return resolve(res);
}
}, function(err){
if(!called){
called = true;
return reject(err);
}
})
})
})
}
使用方式
function fn1(){
return new Promise(resolve => setTimeout(()=>resolve(1), 3000))
}
function fn2(){
return new Promise(resolve => setTimeout(()=>resolve(2), 2000))
}
Promise.race([fn1(), fn2()]).then(res=>console.log(res), err=>console.log(err))
// 2
用于包装任意对象为promise对象,返回一个新的promise,并且状态是resolved
Promise.resolve = function(value){
if(value instanceof this) return value;
return executeCallback.bind(new this())('resolve', value);
}
用于包装任意对象为promise对象,返回一个新的promise,并且状态是rejected
Promise.reject = function(value){
if(value instanceof this) return value;
return executeCallback.bind(new this())('reject', value);
}
用于一个promise任务结束后等待指定的时间再去执行一些操作
Promise.prototype.wait = function(ms){
var P = this.constructor;
return this.then(function(v){
return new P(function(resolve, reject){
setTimeout(function(){ resolve(v); }, ~~ms)
})
}, function(r){
return new P(function(resolve, reject){
setTimeout(function(){ reject(r); }, ~~ms)
})
})
}
使用
fn1().wait(2000).then(res=>console.log(res),err=>console.log(err))
这里考虑到,wait
是用于promise实例对象上的,那么为了可以保证链式调用,必须返回一个 新的promise
,并且上一步的成功和失败的消息不能丢失,继续向后传递,这里只做延迟处理。
用于中断promise链
通常在 promise链
中去reject或throw,或者是异常报错信息,promise内部都会使用 try...catch
转换为 reject
方法往后传递,无法中断后面的 then
或其它方法的执行,那么这里利用,then
方法中对状态的要求必须不是 Pending
状态的处理才会立即执行回调,在 promise链
中返回一个初始状态的 Promise对象
,便可以中断后面回调的执行。
Promise.stop = function(){
return new this();
}
使用
Promise
.resolve(1)
.then(res=>{
console.log('发生错误,停止后面的执行')
return Promise.stop();
})
.then(res=>console.log(res))
.catch(err=>console.log(err))
无论成功还是失败最终都会调用 always
中注册的回调
Promise.prototype.always = function(fn){
return this.then(function(v){
return fn(v), v;
}, function(r){
throw fn(r), r;
})
}
使用
ajaxLoadData()
.then(res=>console.log(res), err=>console.log(err))
.always(()=>console.log('关闭loading动画'))
由于promise在执行 resolve
或 onResolved
回调时,使用了try...catch
,并将错误信息,使用 reject方法
传递了出去,但是如果后面没有注册处理reject的回调函数,那么错误信息将无法得到处理,进而消失不见,难以查觉,所以有了 done
方法。
done
方法并不返回promise对象,也就是done之后不能使用 then
或catch
了,其主要作用就是用于将 promise链
中未捕获的异常信息抛至外层,并不会对错误信息进行处理。
done方法必须应用于promise链的最后
Promise.prototype.done = function(onResolved, onRejected){
this.then(onResolved, onRejected).catch(function (error) {
setTimeout(function () {
throw error;
}, 0);
});
}
使用
ajaxLoadData()
.then(res=>{
return new Promise((resolve,reject)=>reject('未捕获的错误'))
}, err=>console.log(err))
.always(()=>console.log('关闭loading动画'))
.done()//这里会将错误信息 '未捕获的错误' 抛至外层
Deferred
的简称,叫延迟对象,其实是 new Promise()
的语法糖
与Promise的关系
- Deferred 拥有 Promise
- Deferred 具备对 Promise的状态进行操作的特权方法
- Promise 代表了一个对象,这个对象的状态会在未来改变
- Deferred对象 表示了一个处理没有结束,在状态发生改变时,再使用Promise来处理结果
优缺点
- 不用使用大括号将逻辑包起来,少了一层嵌套
- 但是缺少了Promise的错误处理逻辑
Promise.deferred = Promise.defer = function(){
var dfd = {}
dfd.promise = new this(function(resolve, reject) {
dfd.resolve = resolve;
dfd.reject = reject;
})
return dfd
}
使用
function getURL(URL) {
var deferred = Promise.deferred;
var req = new XMLHttpRequest();
req.open('GET', URL, true);
req.onload = function () {
if (req.status === 200) {
deferred.resolve(req.responseText);
} else {
deferred.reject(new Error(req.statusText));
}
};
req.onerror = function () {
deferred.reject(new Error(req.statusText));
};
req.send();
return deferred.promise;
}
用于判断某些promise任务是否超时 如一个异步请求,如果超时,取消息请求,提示消息或重新请求
Promise.timeout = function(promise, ms){
return this.race([promise, this.reject().wait(ms)]);
}
用法
function fn4(){
return new Promise(resolve=> setTimeout(()=>resolve(1), 3000))
}
Promise
.timeout(fn4(), 2000)
.then(res=>console.log(res), err=>console.log('超时'))
// 这里 fn4需要3s执行完成,这里只准在2s内完成,fn4的执行时间就超时了,会输出 `超时`
用于按顺序执行一系列的promise,接收的函数数组,并不是Promise对象数组,其中函数执行时就返回Promise对象,用于有互相依赖的promise任务
Promise.sequence = function(tasks){
return tasks.reduce(function (prev, next) {
return prev.then(next).then(function(res){ return res });
}, this.resolve());
}
使用
function fn1(){ return new Promise(r=>r(1))}
function fn2(data){ return new Promise(r=>r(1+data))}
function fn3(data){ return new Promise(r=>r(1+data))}
Promise.sequence([fn1,fn2,fn3]).then(res=>console.log(res))
//3
对于自定义的Promise类库,是否符合 Promise/A+ 的标准呢?
社区有一个开源的测试脚本 只需两步,就能检验我们的实现是否符合标准了
//全局安装
npm i -g promises-aplus-tests
//运行测试
promises-aplus-tests Promise.js
下面是我们自定义的Promise类库的测试结果,全部通过