原生es5封装的Promise对象
栏目: JavaScript · 发布时间: 6年前
内容简介:前一阵看了一些关于JS异步操作的文章,发现下面贴代码,包括整个思考过程,会有点长为了说明书写的逻辑,我使用以下几个注释标识,整坨变动的代码只标识这一坨的开头处。
前一阵看了一些关于JS异步操作的文章,发现 Promise 真是个好东西,配合 Generator 或者 async/await 使用更有奇效。完美解决异步代码书写的回调问题,有助于书写更优雅的异步代码。花了几天时间研究了 Promise 的工作机制,手痒痒用es6语法封装了一个 Promise 对象,基本实现了原生 Promise 的功能,现在,用es5语法再写一遍。
实现功能:
- 已实现
Promise基本功能,与原生一样,异步、同步操作均ok,具体包括:-
MyPromise.prototype.then() -
MyPromise.prototype.catch()与原生Promise略有出入 -
MyPromise.prototype.finally() -
MyPromise.all() -
MyPromise.race() -
MyPromise.resolve() -
MyPromise.reject()
-
-
rejected状态的冒泡处理也已解决,当前Promise的reject如果没有捕获,会一直冒泡到最后,直到catch -
MyPromise状态一旦改变,将不能再改变它的状态
不足之处:
- 代码的错误被catch捕获时,提示的信息(捕获的错误对象)比原生Promise要多
测试: index.html
- 这个页面中包含了30个测试例子,分别测试了各项功能、各个方法,还有一些特殊情况测试;或许还有有遗漏的,感兴趣自己可以玩一下;
- 更加友好的可视化的操作,方便测试,每次运行一个例子,右边面板可看到结果;
- 自定义了
console.mylog()方法用来输出结果,第一个参数是当前使用的Promise对象,用以区分输出,查看代码时可忽略,后面的参数都是输出结果,与系统console.log()相似; - 建议同时打开
index.js边看代码边玩; - 同一套代码,上面的
MyPromise的运行结果,下面是原生Promise运行的结果;
收获
then/catch reject
代码
下面贴代码,包括整个思考过程,会有点长
为了说明书写的逻辑,我使用以下几个注释标识,整坨变动的代码只标识这一坨的开头处。
//++ ——添加的代码
//-+ ——修改的代码
第一步,基础功能实现
名字随便取,我的叫MyPromise,没有取代原生的Promise。
- 构造函数传入回调函数
callback。当新建MyPromise对象时,我们需要运行此回调,并且callback自身也有两个参数,分别是resolver和rejecter,他们也是回调函数的形式; - 定义了几个变量保存当前的一些结果与状态、事件队列,见注释;
- 执行函数
callback时,如果是resolve状态,将结果保存在this.__succ_res中,状态标记为成功;如果是reject状态,操作类似; - 同时定义了最常用的
then方法,是一个原型方法; - 执行
then方法时,判断对象的状态是成功还是失败,分别执行对应的回调,把结果传入回调处理。
//几个状态常量
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
function MyPromise(callback) {
this.status = PENDING; //储存状态
this.__succ__res = null; //储存resolve结果
this.__err__res = null; //储存reject结果
var _this = this;//必须处理this的指向
function resolver(res) {
_this.status = FULFILLED;
_this.__succ__res = res;
};
function rejecter(rej) {
_this.status = REJECTED;
_this.__err__res = rej;
};
callback(resolver, rejecter);
};
MyPromise.prototype.then = function(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.__succ__res);
} else if (this.status === REJECTED) {
onRejected(this.__err__res);
};
};
复制代码
到这里, MyPromise 可以简单实现一些同步代码,比如:
new MyPromise((resolve, reject) => {
resolve(1);
}).then(res => {
console.log(res);
});
//结果 1
复制代码
第二步,加入异步处理
执行异步代码时, then 方法会先于异步结果执行,上面的处理还无法获取到结果。
- 首先,既然是异步,
then方法在pending状态时就执行了,所以添加一个else; - 执行
else时,我们还没有结果,只能把需要执行的回调,放到一个队列里,等需要时执行它,所以定义了一个新变量this.__queue保存事件队列; - 当异步代码执行完毕,这时候把
this.__queue队列里的回调统统执行一遍,如果是resolve状态,则执行对应的resolve代码。
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
function MyPromise(callback) {
this.status = PENDING; //储存状态
this.__succ__res = null; //储存resolve结果
this.__err__res = null; //储存reject结果
this.__queue = []; //++ 事件队列
var _this = this;
function resolver(res) {
_this.status = FULFILLED;
_this.__succ__res = res;
_this.__queue.forEach(item => {//++ 队列中事件的执行
item.resolve(res);
});
};
function rejecter(rej) {
_this.status = REJECTED;
_this.__err__res = rej;
_this.__queue.forEach(item => {//++ 队列中事件的执行
item.reject(rej);
});
};
callback(resolver, rejecter);
};
MyPromise.prototype.then = function(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.__succ__res);
} else if (this.status === REJECTED) {
onRejected(this.__err__res);
} else {//++ pending状态,添加队列事件
this.__queue.push({resolve: onFulfilled, reject: onRejected});
};
};
复制代码
到这一步, MyPromise 已经可以实现一些简单的异步代码了。测试用例 index.html 中,这两个例子已经可以实现了。
1 异步测试--resolve 2 异步测试--reject
第三步,加入链式调用
实际上,原生的 Promise 对象的then方法,返回的也是一个 Promise 对象,一个新的 Promise 对象,这样才可以支持链式调用,一直 then 下去。。。 而且, then 方法可以接收到上一个 then 方法处理return的结果。根据 Promise 的特性分析,这个返回结果有3种可能:
MyPromise then
- 第一个处理的是,
then方法返回一个MyPromise对象,它的回调函数接收resFn和rejFn两个回调函数; - 把成功状态的处理代码封装为
handleFulfilled函数,接受成功的结果作为参数; -
handleFulfilled函数中,根据onFulfilled返回值的不同,做不同的处理:- 首先,先获取
onFulfilled的返回值(如果有),保存为returnVal; - 然后,判断
returnVal是否有then方法,即包括上面讨论的1、2中情况(它是MyPromise对象,或者具有then方法的其他对象),对我们来说都是一样的; - 之后,如果有
then方法,马上调用其then方法,分别把成功、失败的结果丢给新MyPromise对象的回调函数;没有则结果传给resFn回调函数。
- 首先,先获取
reject 状态的链式调用的处理思路是类似的,在定义的 handleRejected 函数中,检查 onRejected 返回的结果是否含 then 方法,分开处理。值得一提的是,如果返回的是普通值,应该调用的是 resFn ,而不是 rejFn ,因为这个返回值属于新 MyPromise 对象,它的状态不因当前 MyPromise 对象的状态而确定。即是,返回了普通值,未表明 reject 状态,我们默认为 resolve 状态。
MyPromise.prototype.then = function(onFulfilled, onRejected) {
var _this = this;
return new MyPromise(function(resFn, rejFn) {
if (_this.status === FULFILLED) {
handleFulfilled(_this.__succ__res); // -+
} else if (_this.status === REJECTED) {
handleRejected(_this.__err__res); // -+
} else {//pending状态
_this.__queue.push({resolve: handleFulfilled, reject: handleRejected}); // -+
};
function handleFulfilled(value) { // ++ FULFILLED 状态回调
// 取决于onFulfilled的返回值
var returnVal = onFulfilled instanceof Function && onFulfilled(value) || value;
if (returnVal['then'] instanceof Function) {
returnVal.then(function(res) {
resFn(res);
},function(rej) {
rejFn(rej);
});
} else {
resFn(returnVal);
};
};
function handleRejected(reason) { // ++ REJECTED 状态回调
if (onRejected instanceof Function) {
var returnVal = onRejected(reason);
if (typeof returnVal !== 'undefined' && returnVal['then'] instanceof Function) {
returnVal.then(function(res) {
resFn(res);
},function(rej) {
rejFn(rej);
});
} else {
resFn(returnVal);
};
} else {
rejFn(reason)
}
}
})
};
复制代码
现在, MyPromise 对象已经很好地支持链式调用了,测试例子:
4 链式调用--resolve 5 链式调用--reject 28 then回调返回Promise对象(reject) 29 then方法reject回调返回Promise对象
第四步,MyPromise.resolve()和MyPromise.reject()方法实现
因为其它方法对 MyPromise.resolve() 方法有依赖,所以先实现这个方法。
先要完全弄懂 MyPromise.resolve() 方法的特性,研究了阮一峰老师的ECMAScript 6 入门对于 MyPromise.resolve() 方法的描述部分。 由此得知,这个方法功能很简单,就是把参数转换成一个 MyPromise 对象,关键点在于参数的形式,分别有:
MyPromise thenable then
处理的思路是:
- 首先考虑极端情况,参数是undefined或者null的情况,直接处理原值传递;
- 其次,参数是
MyPromise实例时,无需处理; - 然后,参数是其它
thenable对象的话,调用其then方法,把相应的值传递给新MyPromise对象的回调; - 最后,就是普通值的处理。
MyPromise.reject() 方法相对简单很多。与 MyPromise.resolve() 方法不同, MyPromise.reject() 方法的参数,会原封不动地作为 reject 的理由,变成后续方法的参数。
MyPromise.resolve = function(arg) {
if (typeof arg === 'undefined' || arg === null) { //undefined 或者 null
return new MyPromise(function(resolve) {
resolve(arg);
});
} else if (arg instanceof MyPromise) { // 参数是MyPromise实例
return arg;
} else if (arg['then'] instanceof Function) { // 参数是thenable对象
return new MyPromise(function(resolve, reject) {
arg.then(function (res) {
resolve(res);
}, function (rej) {
reject(rej);
});
});
} else { // 其他值
return new MyPromise(function (resolve) {
resolve(arg);
});
};
};
MyPromise.reject = function(arg) {
return new MyPromise(function(resolve, reject) {
reject(arg);
});
};
复制代码
测试用例有8个: 18-25 ,感兴趣可以玩一下。
第五步,MyPromise.all()和MyPromise.race()方法实现
MyPromise.all() 方法接收一堆 MyPromise 对象,当他们都成功时,才执行回调。依赖 MyPromise.resolve() 方法把不是 MyPromise 的参数转为 MyPromise 对象。
每个对象执行 then 方法,把结果存到一个数组中,当他们都执行完毕后,即 i === arr.length ,才调用 resolve() 回调,把结果传进去。
MyPromise.race() 方法也类似,区别在于,这里做的是一个 done 标识,如果其中之一改变了状态,不再接受其他改变。
MyPromise.all = function(arr) {
if (!Array.isArray(arr)) {
throw new TypeError('参数应该是一个数组!');
};
return new MyPromise(function(resolve, reject) {
var i = 0, result = [];
next();
function next() {
// 对于不是MyPromise实例的进行转换
MyPromise.resolve(arr[i]).then(function (res) {
result.push(res);
i++;
if (i === arr.length) {
resolve(result);
} else {
next();
};
}, reject);
}
})
};
MyPromise.race = function(arr) {
if (!Array.isArray(arr)) {
throw new TypeError('参数应该是一个数组!');
};
return new MyPromise(function(resolve, reject) {
let done = false;
arr.forEach(function(item) {
MyPromise.resolve(item).then(function (res) {
if (!done) {
resolve(res);
done = true;
};
}, function(rej) {
if (!done) {
reject(rej);
done = true;
};
});
})
});
};
复制代码
测试用例:
6 all方法 26 race方法测试
第六步,Promise.prototype.catch()和Promise.prototype.finally()方法实现
他们俩本质上是 then 方法的一种延伸,特殊情况的处理。
MyPromise.prototype.catch = function(errHandler) {
return this.then(undefined, errHandler);
};
MyPromise.prototype.finally = function(finalHandler) {
return this.then(finalHandler, finalHandler);
};
复制代码
测试用例:
7 catch测试 16 finally测试——异步代码错误 17 finally测试——同步代码错误
第七步,代码错误的捕获
目前而言,我们的 catch 还不具备捕获代码报错的能力。思考,错误的代码来自于哪里?肯定是使用者的代码,2个来源分别有:
-
MyPromise对象构造函数回调 -
then方法的2个回调 捕获代码运行错误的方法是原生的try...catch...,所以我用它来包裹这些回调运行,捕获到的错误进行相应处理。
function MyPromise(callback) {
this.status = PENDING; //储存状态
this.__succ__res = null; //储存resolve结果
this.__err__res = null; //储存reject结果
this.__queue = []; //事件队列
var _this = this;
function resolver(res) {
_this.status = FULFILLED;
_this.__succ__res = res;
_this.__queue.forEach(item => {
item.resolve(res);
});
};
function rejecter(rej) {
_this.status = REJECTED;
_this.__err__res = rej;
_this.__queue.forEach(item => {
item.reject(rej);
});
};
try { // -+ 在try……catch……中运行回调函数
callback(resolver, rejecter);
} catch (err) {
this.__err__res = err;
this.status = REJECTED;
this.__queue.forEach(function(item) {
item.reject(err);
});
};
};
MyPromise.prototype.then = function(onFulfilled, onRejected) {
var _this = this;
return new MyPromise(function(resFn, rejFn) {
if (_this.status === FULFILLED) {
handleFulfilled(_this.__succ__res);
} else if (_this.status === REJECTED) {
handleRejected(_this.__err__res);
} else {//pending状态
_this.__queue.push({resolve: handleFulfilled, reject: handleRejected});
};
function handleFulfilled(value) {
var returnVal = value;
// 获取 onFulfilled 函数的返回结果
if (onFulfilled instanceof Function) {
try { // -+ 在try……catch……中运行onFulfilled回调函数
returnVal = onFulfilled(value);
} catch (err) { // 代码错误处理
rejFn(err);
return;
};
};
if (returnVal && returnVal['then'] instanceof Function) {
returnVal.then(function(res) {
resFn(res);
},function(rej) {
rejFn(rej);
});
} else {
resFn(returnVal);
};
};
function handleRejected(reason) {
if (onRejected instanceof Function) {
var returnVal
try {// -+ 在try……catch……中运行onRejected回调函数
returnVal = onRejected(reason);
} catch (err) {
rejFn(err);
return;
};
if (typeof returnVal !== 'undefined' && returnVal['then'] instanceof Function) {
returnVal.then(function(res) {
resFn(res);
},function(rej) {
rejFn(rej);
});
} else {
resFn(returnVal);
};
} else {
rejFn(reason)
}
}
})
};
复制代码
测试用例:
11 catch测试——代码错误捕获 12 catch测试——代码错误捕获(异步) 13 catch测试——then回调代码错误捕获 14 catch测试——代码错误catch捕获
其中第12个异步代码错误测试,结果显示是直接报错,没有捕获错误,原生的 Promise 也是这样的,我有点不能理解为啥不捕获处理它。
第八步,处理MyPromise状态确定不允许再次改变
这是 Promise 的一个关键特性,处理起来不难,在执行回调时加入状态判断,如果已经是成功或者失败状态,则不运行回调代码。
function MyPromise(callback) {
//略……
var _this = this;
function resolver(res) {
if (_this.status === PENDING) {
_this.status = FULFILLED;
_this.__succ__res = res;
_this.__queue.forEach(item => {
item.resolve(res);
});
};
};
function rejecter(rej) {
if (_this.status === PENDING) {
_this.status = REJECTED;
_this.__err__res = rej;
_this.__queue.forEach(item => {
item.reject(rej);
});
};
};
//略……
};
复制代码
测试用例:
-
27 Promise状态多次改变
以上,是我所有的代码书写思路、过程。完整代码与测试代码到 github 下载
参考文章
以上所述就是小编给大家介绍的《原生es5封装的Promise对象》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- ios - 原生骨架库,网络过渡动画封装
- 原生es6封装一个Promise对象
- 原生JS简单封装JSONP跨域获取数据
- ios - 原生骨架屏,网络加载过渡动画的封装
- 基于原生fetch封装一个带有拦截器功能的fetch,类似axios的拦截器
- 封装一个原生js的ajax请求,支持IE9CORS跨域请求
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
疯狂Java讲义
李刚 / 电子工业出版社 / 2012-1-1 / 109.00元
《疯狂Java讲义(附光盘第2版)》是《疯狂Java讲义》的第2版,第2版保持了第1版系统、全面、讲解浅显、细致的特性,全面介绍了新增的Java 7的新特性。 《疯狂Java讲义(附光盘第2版)》深入介绍了Java编程的相关方面,全书内容覆盖了Java的基本语法结构、Java的面向对象特征、Java集合框架体系、Java泛型、异常处理、Java GUI编程、JDBC数据库编程、Java注释、......一起来看看 《疯狂Java讲义》 这本书的介绍吧!