thief系列之四:从实现getJSON中探索XHR和promise
youngwind opened this issue · 0 comments
youngwind commented
问题
我们使用jquery的时候,经常用到的是$.ajax,可以发各种异步请求。不过这次我们先来实现一个更简单的方法,$.getJSON:向服务器发起一个get请求,获取相应的json数据。
分析
我们先来分析一下有哪几个方面需要注意:
- 调用getJSON的方式是T.getJSON,而非T('selector').getJSON,所以,getJSON方法需要直接定义在T的属性上,不能定义在T的原型属性上。
- 需要了解http协议的基本知识 #72
- 如何构造和使用XHR对象
- promise的运用
XMLHttpRequest
一个简单的XHR demo
// 创建一个请求对象
var client = new XMLHttpRequest();
// 启动请求(注意,此处请求仍未发送,只是准备好而已)
client.open('get','https://api.github.com/users/youngwind/starred');
// 发送请求
client.send();
我们如何知道监控请求的进度,知道它什么时候完成呢?xhr对象有个readyState属性,每当请求阶段改变的时候,readyState都会发生改变,从而触发readystatechange事件。代码如下:
T.getJSON = function (url, cb) {
// 创建一个请求对象
var client = new XMLHttpRequest();
// 下面几行代码的顺序很重要
// 必须是先open,然后各种set,最后是send,否则会报错
client.open('get', url);
client.setRequestHeader("Accept", "application/json");
client.responseType = 'json';
client.send();
client.onreadystatechange = function () {
if (this.readyState === 4) {
if (this.status >= 200 && this.status < 300 || xhr.status == 304) {
cb && cb(this.response);
} else {
console.log('error', this.status);
}
}
}
};
promise
ok,目前为止,我们已经成功实现:创建一个xhr对象,向服务器请求json数据,请求成功后传给回调函数进行处理。但是,问题在于,用callback的方法进行异步回调的处理太low了吧。
jquery在1.5.0版本之后开始引入deferred对象用于解决回调问题,可以参考这里。解决回调的方法有很多,比如 #49 #62 , 不妨我们用promise实现一个吧,因为ES6标准都已经原生支持promise了。
一个promise的简单demo
var promise = new Promise(function(resolve, reject){
if(/*异步操作成功*/) {
resolve(value);
} else {
reject(error);
}
})
所以,我们需要将T.getJSON封装成返回一个promise对象,这样就能用.then了。
T.getJSON = function (url) {
var promise = new Promise(function (resolve, reject) {
// 创建一个请求对象
var client = new XMLHttpRequest();
client.open('get', url);
client.setRequestHeader("Accept", "application/json");
client.responseType = 'json';
client.send();
client.onreadystatechange = function () {
if (this.readyState === 4) {
if (this.status >= 200 && this.status < 300 || xhr.status == 304) {
resolve(this.response);
} else {
reject(this.response);
}
}
}
});
return promise;
};
使用例子:
T.getJSON('https://api.github.com/users/youngwind/starred')
.then(function(res){
console.log(res);
});
promise.all与promise.race
如果我们有这样的需求:请求A、B、C都完成之后再调用回调函数,又或者这样的需求:请求A、B、C任意一个率先完成都会调用回调函数,那该怎么处理呢?这种情况使用promise.all和promise.race可以轻松解决。
/**
* 请求json数据
* @param url {string}/{Array} 请求url(数组)
* @param isAll {boolean} 如果url是数组,那么此状态位有用,默认为true
* 如果为true,则所有请求都结束之后再返回,传递给resolve的数据是所有请求返回数据构成的数组
* 如果为false,则任一请求结束之后都会返回,传递给resolve的数据是那个率先完成的请求返回的数据
* @returns {Promise}
*/
T.getJSON = function (url, isAll) {
if (!Array.isArray(url)) {
return generatePromiseXHR(url);
}
var promises = url.map(function (subUrl) {
return generatePromiseXHR(subUrl);
});
if (isAll) {
return Promise.all(promises);
} else {
return Promise.race(promises);
}
function generatePromiseXHR(url) {
var promise = new Promise(function (resolve, reject) {
// 创建一个请求对象
var client = new XMLHttpRequest();
client.open('get', url);
client.setRequestHeader("Accept", "application/json");
client.responseType = 'json';
client.send();
client.onreadystatechange = function () {
if (this.readyState === 4) {
if (this.status >= 200 && this.status < 300 || xhr.status == 304) {
resolve(this.response);
} else {
reject(this.response);
}
}
};
});
return promise;
}
};