2018-1-24 作者: 张子阳 分类: Web前端
JavaScript是单线程执行的,因此,为了避免操作时的页面中断(体现为页面假死),可以使用回调函数。但是如果回调函数中仍然嵌套有回调函数,代码就会变得越来越不可维护。这篇文章介绍ES6如何通过Promise解决这个问题,并介绍了相关的Fetch方法。
假设有一个服务接口,接受两个操作数,可以完成相加的操作,并以Json字符串返回结果。返回的结果结构如下:
{
Data: 3, // 计算结果
IsSuccess: true, // 是否成功
Message: "", // 错误消息
State:0 // 执行的结果状态
}
本例中仅需要关注Data,其他3个值总是正确的。
那么,当要完成1+2+3+4这个操作时,如果采用传统的方式,代码可能是这样的:
function post(url, data, sucess, err){
var request = new XMLHttpRequest();
request.onreadystatechange = function(){
if(request.readyState == 4) {
if(request.status == 200){
sucess(request.responseText);
}else if(typeof err == "function"){
err(request.status, request.responseText)
}
}
}
request.open("POST", url, true);
request.send(JSON.stringify(data))
}
post("http://test.tracefact.net/compute/add", {x:1, y:2}, function(res){
res = JSON.parse(res);
var z = res.Data; // 3
post("http://test.tracefact.net/compute/add", {x:z, y:3}, function(res){
res = JSON.parse(res);
var z = res.Data; // 6
post("http://test.tracefact.net/compute/add", {x:z, y:4}, function(res){
res = JSON.parse(res);
var z = res.Data; // 10
alert(z);
});
});
});
http://test.tracefact.net/compute/add接口是真实存在并且可以被调用的。
可以看到,success回调函数调用了3次,嵌套了2层。简单起见,没有传入err回调函数。可以通过将url修改为错误的,或者将x传入字符串来制造错误,这里就不演示了。
ES6引入了Promise来解决这个问题,简单来说,Promise将一层套一层的的回调,改成链式操作。
Promise共有三种状态:pending(初始状态,既不是成功,也不是失败状态)、fulfilled( 意味着操作成功完成)、rejected(意味着操作失败)。
Promimse的构造函数接受一个函数,这个函数的两个参数分别称作resolve方法和reject方法。当任务成功时,调用resolve()方法,失败时,调用reject()方法。调用resolve和reject时,传入的值,将作为输入参数,传递到then方法的resolve和reject中。
在Promise对象上可以调用then()方法,它也接受两个方法,一个是resolve,一个是reject。then()方法返回的还是一个Promise对象,因此支持链式调用。值得注意的是:then方法中上一个resolve方法的返回值,将成为下一个then方法中resolve的输入参数。由此构成了数据的流动。
上面两段话如果不结合代码,很难理解清楚,我们继续看1+2+3+4这个例子,为了简单起见,先不使用post方法异步操作。
var p = new Promise(function(resolve, reject){
var x = 1, y =2;
resolve(x+y); // 传入结果3
});
以上代码是立即执行的,仅仅完成了1+2。那么如何利用这个计算结果完成下一步+3的操作呢?可以通过then方法。
var p = new Promise(function(resolve, reject){
var x = 1, y =2;
resolve(x+y);
});
p.then(function(z){ // 这里z是上一步的计算结果3
var y = 3;
return z+y; // 6
})
类似地,完成+4操作,只需要再then一次就可以了。
p.then(function(z){
var y = 3;
return z+y; // 6
}).then(function(z){
var y = 4;
return z+y; // 10
})
接下来,我们改下post的例子,将它变成Promise版本的。
var p = new Promise(function(resolve, reject){
var x = 1, y =2;
post("http://test.tracefact.net/compute/add", {x:1, y:2}, function(res){
res = JSON.parse(res);
console.log("step1:", res);
resolve(res.Data); // 3
})
});
p.then(function(z){
return new Promise(function(resolve, reject){
post("http://test.tracefact.net/compute/add", {x:z, y:3}, function(res){
res = JSON.parse(res);
console.log("step2:", res);
resolve(res.Data); // 6
});
});
}).then(function(z){
return new Promise(function(resolve, reject){
post("http://test.tracefact.net/compute/add", {x:z, y:4}, function(res){
res = JSON.parse(res);
console.log("step3:", res);
resolve(res.Data); // 10
});
});
})
resolve用来处理正常流程,reject则用来处理失败的情况,用法和resolve是类似的,例如下面,我们将x的参数改为“s”,服务端将会返回400 bad request,此时可以添加then的第二个参数reject进行处理:
var p = new Promise(function(resolve, reject){
var x = 1, y =2;
post("http://test.tracefact.net/compute/add", {x:"s", y:2}, function(res){
res = JSON.parse(res);
console.log("step1:", res);
resolve(res.Data); // 3
}, function(state, msg){
reject(state + " " + msg)
})
});
p.then(function(z){
return new Promise(function(resolve, reject){
post("http://test.tracefact.net/compute/add", {x:z, y:3}, function(res){
res = JSON.parse(res);
console.log("step2: ", res);
resolve(res.Data); // 6
});
});
}, function(msg){
alert("step2: " + msg)
});
除了使用reject,还有一种方式,就是使用catch方法,示例如下:
p.then(function(z){
return new Promise(function(resolve, reject){
post("http://test.tracefact.net/compute/add", {x:z, y:3}, function(res){
res = JSON.parse(res);
console.log("step2: ", res);
resolve(res.Data); // 6
});
});
}).catch(function(msg){
alert("step2: " + msg)
});
一般来说,使用catch会更简单清晰一些。
在过去,因为缺乏统一的标准,发起ajax异步请求,在不同的浏览器下有不同的方式,主要是使用XMLHttpRequest对象和ActiveXObject("Msxml2.XMLHTTP")。在ES6中,提供了fetch方法简化了这一操作。除此以外,fetch方法返回的是一个Promise对象,因此,可以链式发起异步请求。而服务端的返回值则通过response对象传递。
再次改写上面的例子:
fetch("http://test.tracefact.net/compute/add", {
method:"POST",
headers:{ "Content-Type": "application/json" },
body: JSON.stringify({x:1, y:2})
}).then(function(res){
return res.json();
}).then(function(res){
return fetch("http://test.tracefact.net/compute/add", {
method:"POST",
headers:{ "Content-Type": "application/json" },
body: JSON.stringify({x:res.Data, y:3})
})
}).then(function(res){
return res.json();
}).then(function(res){
console.log(res.Data);
}).catch(function(err){
alert("err: " + err);
});
使用fetch时第一步then返回的response对象(res),和直接使用前面post方法返回的res并不是同一个对象。这个对象的详细内容可以参考这里:https://developer.mozilla.org/zh-CN/docs/Web/API/Response。在这个response上调用json()方法,返回的也是一个Promise,然后再下一步then才能够获得服务器返回的原始对象。
这篇文章主要讲述了ES6中的Promise对象和Fetch方法,上面的代码,无需Babel就可以在新版本Chrome浏览器下直接运行,建议想要熟悉的朋友们敲一遍代码,执行一遍以加深理解。
感谢阅读,希望这篇文章能给你带来帮助!