1为啥要用promise?
js是单线程的,理论上所有代码都是同步的,从上到下一行行执行。然而就这样傻傻解析运行js的话,碰到较重的任务时,会阻塞进程,如发送一个用户是否登录验证请求,请求完成响应之前,下面的动作都不会执行,伪代码如下:
//获取XMLHttpRequest对象并发送请求
var xhr = new XMLHttpRequest()
xhr.open(method, url, async);
xhr.send(data);
//漫长的响应等待......
//根据用户信息发送请求获取对应数据
//使用数据渲染table列表
//获取按钮,绑定单击事件
//......
用户是否登录验证请求完成响应之前,后续所有任务都不会执行,用户使用体验极差。
于是聪明的程序员将任务分为两类:
- 同步任务
-
异步任务(如网络请求,加载图片等)
同步任务由上到下,逐行执行,异步的进入Event Table并注册函数,过后Event Table会将这个函数移入Event Queue,等主线程中的同步任务都执行完后主线程进入Event Queue执行异步代码。该过程示意图如下:
同步代码与异步代码的执行过程
修改上面的代码得到:
//获取XMLHttpRequest对象并发送请求
var xhr = new XMLHttpRequest()
xhr.open(method, url, async);
xhr.onloadend = function () {
//漫长的响应等待......
}
xhr.send(data);
//根据用户信息发送请求获取对应数据
//使用数据渲染table列表
//获取按钮,绑定单击事件
//......
如此,漫长的网络请求过程就不会阻塞之后的//根据用户信息发送请求获取对应数据 //使用数据渲染table列表 //获取按钮,绑定单击事件 //......
等任务了,等它们都执行完才会去执行等待网络请求响应的任务。
但上面的逻辑有个bug,就是这两个任务//根据用户信息发送请求获取对应数据//使用数据渲染table列表
应该在用户是否登录的验证请求完成之后进行,而为了使该请求不阻塞后续任务已经将其修改为异步执行了,那么应该放在其后并依赖它的返回结果的任务应该放置它的回调函数里去,伪代码如下:
//获取XMLHttpRequest对象并发送请求
var xhr = new XMLHttpRequest()
xhr.open(method, url);
xhr.onloadend = function () {
//漫长的响应等待......
//根据用户信息发送请求获取对应数据
xxx.onloadend = function(){
//使用数据渲染table列表
function renderData(){
}
}
}
xhr.send(data);
//获取按钮,绑定单击事件
//......
如此简单的逻辑已经使用了两层异步函数嵌套,再加上内层的渲染函数,三层了!!!,业务逻辑再复杂点,嵌套层数会疯狂增加,如此代码已经失去了可阅读性。
于是聪明的程序员发明了promise
2 promise如何使用 使函数嵌套变得清晰
所谓Promise,简单说就是一个容器,一个对象,可以获取异步操作的消息,并可在后续语法操作then中使用异步操作的返回结果,
伪代码如下:
new Promise(function(resolve,reject){
//异步操作
//。。。
if(成功){
resolve(data)
}else{
reject(data)
}
}).then(function(data){
//这里可以拿到data
})
我们使用promise将ajax请求简单封装一下,看看其如何使用:
常规的ajax请求分四步:
//获取XMLHttpRequest对象并发送请求
var xhr = new XMLHttpRequest();
//准备连接与参数
xhr.open(method, url);
//监听响应事件(当然还有其他事件)
xhr.onloadend = function () {
}
//发送请求
xhr.send(data);
使用promise对其进行封装的伪代码如下:
function ajaxPromise(url, method, data) {
var xhr = new XMLHttpRequest()
return new Promise(function (resolve, reject) {
xhr.open(method, url, async);
xhr.timeout = options.timeout;
xhr.onloadend = function () {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304)
resolve(xhr);//成功时使用resolve将数据返给后续操作
else
reject(xhr)//失败时使用resolve将数据返给后续操作
}
xhr.send(data);
})
}
那么本文开头的业务逻辑可改写成下面的伪代码:
//发送用户是否登录请求
ajaxPromise(url, method, data)
.then(function(data){
//根据用户信息发送请求获取对应数据
return ajaxPromise(url, method, data)
})
.then(function(data2){
//使用数据渲染table列表
})
//获取按钮,绑定单击事件
//......
原来极其难以阅读的函数嵌套,变成了清晰的几个.then
下面是详细的对ajax的封装:
function requestNumber(url, method, data, async, timeout) {
var xhr = new XMLHttpRequest()
return new Promise(function (resolve, reject) {
xhr.open(method, url, async);
xhr.timeout = options.timeout;
xhr.onloadend = function () {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304)
resolve(xhr);
else
reject({
errorType: 'status_error',
xhr: xhr
})
}
xhr.send(data);
//错误处理
xhr.onabort = function () {
reject(new Error({
errorType: 'abort_error',
xhr: xhr
}));
}
xhr.ontimeout = function () {
reject({
errorType: 'timeout_error',
xhr: xhr
});
}
xhr.onerror = function () {
reject({
errorType: 'onerror',
xhr: xhr
})
}
})
}
3多个网络请求的几种关系的处理
- 链式关系
使用promise的then将其串联
request1().then(return request2).then(return request3)...... - 并列关系
- 并列都成功才执行后续动作
Promise.all([request1,request2,request3...]).then(function([data1,data2,data3]){
//后续动作
}) - 并列并只要有一个成功就执行后续动作
Promise.race([request1,request2,request3...]).then(function([data1,data2,data3]){
//后续动作
})
4 使用promise封装ajax的弊端
这得从javaScript的执行机制说起。
事件循环--同步任务先执行,异步任务放队列。
同步任务执行完再执行异步任务,异步任务里也可能有同步任务和异步任务。
先执行同步再执行异步、、、、、、
如此循环,最终执行完整个脚本逻辑。
任务还分宏任务和微任务。
宏任务是宿主环境提供的,如来自window的setTimeout ,ajax等。
微任务是语言本身提供的,如promise等。
一个javascript脚本的大致执行顺序如下:
- 整个javascript脚本上同步代码的执行作为一个宏任务最先被执行。
- 其后的setTimeout setInterval ajax等被放入任务队列后续执行。
- 而promise作为一个微任务,其执行顺序是上一个宏任务执行之后,下一个宏任务执行之前。
- 无论宏任务还是微任务,其中也可能包含同步代码和异步代码,于是依据上述3步再开启一个循环。
根据上述原因,封装到promise的ajax代码,其发送请求的那三步(new open send)会被延后到整个脚本同步代码执行完,而且将响应回调函数延后到现有任务队列的最后,如果大量使用那么会大大降低请求效率。这就是使用promise封装ajax的弊端,所以那些没有串并联关系的请求尽量使用原生的ajax做请求。
ES6总篇--目录
网友评论