Promise
Promise
Promise已经获得所有现代浏览器的支持,是时候使用它来重构你的代码了。
Promise可以改变我们代码的书写方式,让代码的逻辑和结构更加清晰,帮助你从回调地狱中解脱。
回调模式
下面是一个简单的例子,如果你还不知道它是用来干什么的话。
function get(url,callback){
var xhr = new XMLHttpRequest();
xhr.open('GET',url,true);
xhr.onload=function(){
if(xhr.status===200){
callback(null,xhr.response);
}else{
callback(Error(xhr.statusText));
}
};
xhr.onerror=function(){
callback(Error("Network Error!"));
};
xhr.send();
}
function log(err,data){
if(err) return console.log(err);
console.log(data)
}
get("someurl...",log);
上面的函数通过ajax请求获取指定url返回的数据,并且在控制台打印出来。因为要在ajax请求成功,并且返回数据之后,我们才能执行log函数,所以,按照习惯,我们使用了回调函数。
现在,如果有一个新的需求,我们在log函数执行之后,还要执行另一个操作,比如说将ajax请求返回的结果进行一系列的处理。
function handleData(data){
......
}
按照常规的想法,我们需要把这个函数当作回调传入到log函数中,这样,当打印完毕后,再执行handleData。那么,log函数需要做一些修改:
function log(err,data,callback){
if(err) return console.log(err);
console.log(data);
callback && callback(data);
}
由于log函数是作为回调传入get函数中的,log函数自身也有一个函数参数作为回调,我们需要在get函数中传入它。
function get(url, callback, logcallback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onload = function() {
if (xhr.status === 200) {
callback(null, xhr.response, logcallback);
} else {
callback(Error(xhr.statusText), null, logcallback);
}
};
xhr.onerror = function() {
callback(Error("Network Error!"));
};
xhr.send();
}
现在我们的流程设计完毕,开始调用:
get("someurl",log,handleData);
如果我们的数据需要经过一系列的处理,那么这个回调参数的个数会越来越多。
使用Promise
同样的需求使用Promise来解决,代码如下:
get("someurl").then(log).then(handleData);
代码是不是变简洁而又清晰?不过,在实现这样的代码之前,我们要对以上的函数做出一些修改,让他们华丽的变身。
首先,我们要修改get函数:
function get(url){
var promise = new Promise(function(resolve,reject){
var xhr = new XMLHttpRequest();
xhr.open('GET',url,true);
xhr.onload=function(){
if(xhr.status===200){
resolve(xhr.response);
}else{
reject(Error(xhr.statusText));
}
};
xhr.onerror=function(){
reject(Error("Network Error!"));
};
xhr.send();
});
return promise;
}
可以看到,get函数现在返回了一个promise对象。而promise对象具有then方法,所以我们可以get函数返回promise之后,直接调用它的then方法,then方法也返回一个promise对象,所以可以继续调用then方法,如果这方面你还不是很懂的话,想想jquery的链式调用吧!
promise对象的生成
在这里我们使用的是最基本的方法,通过构造函数:
var promise = new Promise(function(resolve,reject){
......
});
省略掉的部分就是你原本的代码逻辑,唯一不同的一点,就是对于返回值的处理。
对于ajax请求来说,有两种状态:请求成功,请求失败。 当请求成功时,我们把数据返回,不过这里用resolve 代替return。
resolve(xhr.response);
当请求失败时,我们把错误返回,这里用reject代替return。
reject(xhr.statusText);
我们暂且不考虑其他情况。
resolve 和 reject
它们代替了我们平时所使用的return,这样做的目的是什么?
我们知道,ajax请求是需要花费时间的。我们要等待请求并返回结果。 当结果返回时,不管是数据或者错误,这个请求都是确确实实的发生并且完成了。而resolve和reject就是把这个promise对象的状态进行改变,由等待状态改变至完成状态。
如果都是把promise对象的状态改变为完成状态的话,为什么要用两个不同的名称呢,都用resolve不就行了嘛?
我们知道,ajax请求完成了,但是完成也有两种状态。一种是成功获取到数据的完成,一种是请求出错的完成。但是这两种完成都是完成,都经历了发送请求,等待响应,返回结果的过程。
当以resolve方式改变promise对象的状态至完成状态时,会调用后续的then方法中第一个参数,做为回调。 当以reject方式改变promise对象的状态至完成状态时,调用then方法中第二个参数作为回调,或者catch方法中的参数作为回调。
get("someurl").then(log,logerr);
get("someurl").then(log).catch(logerr);