目录
1、更友好地处理异步函数的异常问题
2、更友好地处理异步函数的返回值
3、解决地狱回调函数(Callback Hell)
4、看起来更像同步代码编程风格
5、隔离变化点
6、then方法返回一个新实例
7、实现链式调用风格
Promise简介
Promise是有“承诺”之意;
承诺解释:人与人之间,一个人对另一个人所说的具有一定憧憬的话,一般是可以实现的。对某项事务答应照办。(来自百度词条)
Promise A+是什么?
An open standard for sound, interoperable JavaScript promises—by implementers, for implementers.
译:它一个开放标准;它是由互操作JavaScript语言Promises实现的;
Promise A+只提供了一个then方法,但允许实现者实现其它的方法更多同时要遵循一定的规范;
其它方法如:catch()、finally()、all()、race()、allSettled()、any()、resolve()、reject()、try()等等;
更多详情查看Promise A+标准;
Promise
Promise 是异步编程的一种解决方案,比传统的异常编程解决方案——回调函数和事件——更安全、更合理、更强大;接下来我们将通过Promise特点来分析Promise的更安全、更合理、更强大
特性;
Promise的特点
1、更友好地处理异步函数的异常问题
为了更好的说明异步函数异常问题,我们先看一下同步函数是如何报出异常的;
接下来看一段同步函数伪代码:
同步函数伪代码1、调用foo函数进入foo函数体;
2、在foo函数体内调用callback(bar)函数
3、a/b会抛出b未定义的异常, 在bar函数体中没有捕获它的catch,向调用这的地方抛出;
4、来到了foo函数体的callback,在foo函数体中没有捕获它的catch,继续向调用foo的地方报出;
5、在调用foo函数处发现有有try/catch,就会调用catch语句把错误传给它;
同步函数是当一个函数的函数体内的代码执行完毕后,返回了结果者会继续向下执行;
所以异常情况会得到了捕获;
接下来看一段同步函数伪代码:
在ES6之前是没有办法声明异步函数的,javascript提供的都是内置的异步函数,如:setTimeout是一个异步函数,我们测试一下setTimeout是否能够提出异常?
首先把上面代码拷贝下来,然后把foo函数
改成setTimeout函数
并且删除无用foo函数
,
没有捕获成功,抛出异常了,异步函数不支持“冒泡”抛出异常;所以在调用处捕获不到它;
异步函数无法捕获异常情况,在程序中是不允许的,我们要想办法解决它?
Promise可以帮助我们更好的解决这个问题;
接下来看一段同步函数伪代码:
function bar(a) {
a / b;
}
// 这是一个用Promise模拟的异步函数;
function asyncFn(callback, number) {
return new Promise((resolve, reject) => {
callback();
});
}
let promise = asyncFn(bar, 10);
promise.catch((data) => {
console.log("抛出的异常我捕获到了!");
});
asyncFn相当于setTimeout方法;不一样的是这个方法会返回一个Promise对象,调用对象的catch方法就可以捕获到异常;
用支持Promise的asyncFn替换setTimeout函数;说明Promise可以捕获到抛出的异常;
这个async是我们模拟的异步函数;这个函数看起来有点复杂,如果是一个javascript提供的原生异步函数不会向上面这样还需要实例化一个Promise对象,异步函数看起来会更像这样的伪代码:
function bar(a) {
a / b;
}
// 这是一个用Promise模拟的异步函数;
async function asyncFn(callback, number) {
callback();
}
let promise = asyncFn(bar, 10);
promise.catch((data) => {
console.log("抛出的异常我捕获到了!");
});
注意:以上代码是伪代码,为了演示捕获异步函数抛出的异常的特点
只考虑了一种情况就是抛出异常;
2、更友好地处理异步函数的返回值
我们看一个ajax请求的问题
function ajax(url, method, callback) {
var xhr = new XMLHttpRequest();
xhr.responseType = "json";
xhr.open(method, url, true);
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
callback(xhr.response);
}
}
}
ajax("https://www.jianshu.com/p/7c53c95d664f", "get", function (response)
var content =response;
/**************************************************/
/*********这里省略掉的是对content的业务处理代码*********/
/**************************************************/
});
通过这种方式我们可以正确地得到了content,同时也可以做下面的业务处理;
这种方式能满足我们的要求,但不是很合理?
首先我们分析这种方式为什么不合理?
第一、使用回调函数会增加人我们对ajax理解复杂度,原因是,从ajax函数的角度来看,当我调用了ajax函数并输入了正确的参数,执行完函数体后,就应该得到函数的返回值(同步函数就是很直接这么干的,哈哈!)才对,并不希望再加一个回调函数;
第二、从重构代码的角度来看,一个函数最好不要有参数,最坏不要超过三个参数,如果超过了就应该想办法重构它,参数越多函数的复杂度就越大;
接下来看一段Promise伪代码:
function ajax(url, method) {
return new Promise((resolve) => {
var content;
var xhr = new XMLHttpRequest();
xhr.responseType = "json";
xhr.open(method, url, true);
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
resolve(xhr.response);
}
}
return content;
});
}
var promise = ajax("https://www.jianshu.com/p/7c53c95d664f", "get");
promise.then(function (response) {
var content = response;
/**************************************************/
/*********这里省略掉的是对content的业务处理代码*********/
/**************************************************/
});
这种方式比使用回调函数的解决方案更合理,我们来对比一下;
解决方案 | 参数 | 返回值 |
---|---|---|
回调函数 | 参数多 | 在回调函数中获取 |
promise | 参数少 | 在ajax的返回对象(promise)中更获取,更像是直接返回了值 |
注意:这里没有考虑ajax的异常情况
3、解决地狱回调函数(Callback Hell)
地狱回调是更友好地处理异步编程的重头戏,也是大家常常提起的一大亮点;在这里我不过多的分析它,原因是大家都已经很熟悉它了;在面试中也会常常问题;
好我们现在开始说一说地狱回调;
在进行HTTP的API编程时会常常用到它;
假设,我们有三个HTTP API的请求,这三个请求的逻辑是:第一个API的结果是第二个API的请求参数,第二个API的结果是第三个API的请求参数;怎么解决它呢?
有三个解决办法:
第一、把第三个API请求体放到第二个API请求的onreadystatechange
函数中,把第二个API请求体放到第一个API请求的onreadystatechange
函数中;这是极力不推荐的;这也是最严重的地狱回调方式,相信很少有人这么干。这里就不给出伪代码了,相信你已经想像出它的代码结构了;
第二、就是把ajax请求封装起来,就像前面那些使用ajax函数对XMLHttpRequest
封装,把需要的参数输入参数中,这样提高代码可重用性;这个也不给出代码了,相信大家以前也这么干过。
第三、使用 Promise应该是大家现在常用的方式,包括在Node.js的异常编程用的最多了。
如果我们使用Promises处理相同的操作,因为我们可以附加回调而不是传递回调;
接下来看一段伪代码:
function ajax(request) {
return new Promise((resolve) => {
var xhr = new XMLHttpRequest();
xhr.responseType = "json";
xhr.open(request.method, request.url, true);
xhr.send(request.data);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
resolve(xhr.response);
}
}
});
}
// 这只是实现嵌套API调用的其中一种方式而已;
Promise.resolve()
.then(() => {
let request = {
url: "第一个API地址",
method: "post",
data: null
};
return ajax(request)
})
.then((response) => {
let request = {
url: "第二个API地址",
method: "post",
data: response.data // 在实际的开发中不会这么干,还要再做进一步处理,这里只是演示;
}
return ajax(request)
}).then((response) => {
let request = {
url: "第三个API地址",
method: "post",
data: response.data // 在实际的开发中不会这么干,还要再做进一步处理,这里只是演示;
}
return ajax(request)
}).then((data)=>{
console.log(data);
});
这种方式可读性更强;在这里不过多的解释了,如果有不明白的地方,就请留言吧!
前也许有的前端同学会问,这个方式一看就能明白,自己平时学习时也是这么理解的,为什么在工作中想不起来用它,或者就用不到这么多的then处理;这是很正常的。
原因1:和项目有关,如果你的项目小型的并且是单体系统的话,请求一个API就能满足了你想要的数据,不需要像上面这么嵌套多个API;
原因2:如果是一个大系统的话,后端开发同学已经帮前端同学做了一定的处理,前端同学只需要调用一个API请求,原因在于,如果让前端同学处理的话,会产生高延迟,降低响应速度,对用户来说体验也不佳;
对于上面的二点原因,任何事情都不是绝对的而是相对的情况,无认是大系统还是小系统都和架构有关,还要根据项目综合考虑;
单体系统:指所有的业务逻辑都放在一个服务器上,像一个普通的网站就一个最简单的单系统
4、看起来更像同步代码编程风格
这个特点与解决地狱回调函数
特点很相似,但是出发点不同;
-
解决地狱回调函数
也符合实现同步函数编程风格
的特点; - 但
实现同步函数编程风格
不仅仅是为了解决地狱回调函数
特点而存在,还有其它的原因;
如果一个异步函数只调用了一个then或catch方法也符合这个特点。这个特点要和传统的回调函数想相比,虽然都是异步编程,但是他们的编程风格不一样。
这里就不给出代码了。
( 模块化有多种方式,如,函数、对象、文件;这里模块特指是函数)
5、隔离变化点
简单来说,隔离变化点的意思就是要分开处理,把稳定的部分和变化的部分分离开来,只维护变化的部分;
看一下下面的伪代码:
function ajax(request) {
return new Promise((resolve) => {
var xhr = new XMLHttpRequest();
xhr.responseType = "json";
xhr.open(request.method, request.url, true);
xhr.send(request.data);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
resolve(xhr.response);
}
}
});
}
let promise = ajax({
url: "请求地址",
method: "post",
data: null
}).then(() => {
console.log(data.name);
for(var i = 0; i < data.project.length; i++) {
console.log(data.project[i].name)
}
for(var i = 0; i < data.project.length; i++) {
console.log(data.work[i].name)
}
});
这段代码实现了隔离变化点;
从代码中看到,ajax函数的代码是相对稳定的,无论调用多少个api都不需要改这里的东西。
请求参数和调用promise的then方法是变化的,如果以后维护它或处理响应的逻辑都不需要修改ajax函数部分,从代码的角度来看,提高了复用性;
隔离变化点也降低了耦合性,提高了内聚性;
再多说一句,如果ajax的函数中的XMLHttpRequest想换成fetch对象或axios对象,就会变得很方便了,不需要修改下面的业务逻辑代码。
6、then方法返回一个新实例
Promise实现了Promise A+开发标准,标准只提供了一个then抽象方法;调用then方法后一般情况都会返回一个新实例,由于这个特点的存在,同样也实现同步函数编程风格、链式调用风格。
其实代码和前面的很相似,这里不给代码了。
这个具体的使用可以参考 Promise A+,这里有更详细的说明;
7、实现链式调用风格
当你看到这个特点时,已经不需要我再多说什么了!以上几个特点已经很明白地说明了实现链式调用风格。同步函数也有链式调用风格;
我们仍然在Promises中使用回调函数,但是以不同的方式(链接)使用。
这里再多说一下,链式调用是不是有点像组合函数; 一个函数的输出是别一个函数的输入
总结
- 以上几个特点它们有关联性,实现了一个特点,其它的几个特点也相应地实现,如:解决地狱回调,隔离变化点、同步函数编程风格、链式调用风格等;
- 以上几个特点可以再归类,这里没有归类的原因是想表达出更详细的特点,方便大家理解;
- 我们理解Promise对象,对我们以后使用async异步函数是有很大帮助的;
缺点
- Promise在链式调用的过程中不能中途中止;
以上就是我在使用Promise时需要考虑的几点;自己也在学习中。。。有不足的地方欢迎大家留言指正,谢谢!
(完)
网友评论