ajax
ajax是网页与服务端进行数据交互的一种技术。 我们可以通过服务端提供的接口,通过ajax向服务端请求我们需要的数据。整个过程的简单实现如下:
// 简单的ajax原生实现
// 由服务端提供的接口
var url = 'https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-02-26/2017-06-10';
var result;
var XHR = new XMLHttpRequest();
XHR.open('GET', url, true);
XHR.send();
XHR.onreadystatechange = function() {
if (XHR.readyState == 4 && XHR.status == 200) {
result = XHR.response;
console.log(result);
}
}
在ajax的原生实现中,利用了onreadystatechange事件,当该事件触发并且符合一定条件时,才能拿到我们想要的数据,之后我们才能开始处理数据。
这样做看上去并没有什么麻烦,但是如果这个时候,我们还需要做另外一个ajax请求,这个新的ajax请求的其中一个参数,得从上一个ajax请求中获取,这个时候我们就不得不如下这样做:
var url = 'https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-02-26/2017-06-10';
var result;
var XHR = new XMLHttpRequest();
XHR.open('GET', url, true);
XHR.send();
XHR.onreadystatechange = function() {
if (XHR.readyState == 4 && XHR.status == 200) {
result = XHR.response;
console.log(result);
// 伪代码
var url2 = 'http:xxx.yyy.com/zzz?ddd=' + result.someParams;
var XHR2 = new XMLHttpRequest();
XHR2.open('GET', url, true);
XHR2.send();
XHR2.onreadystatechange = function() {
...
}
}
}
当出现第三个ajax(甚至更多)仍然依赖上一个请求的时候,我们的代码就变成了一场灾难。我们需要不停的嵌套回调函数,以确保下一个接口所需要的参数的正确性。这样的灾难,我们称之为 回调地狱。
因此,我们需要一个叫做Promise
的语法来解决这样的问题。
当然,除了回调地狱之外,还有一个非常重要的需求:为了我们的代码更加具有可读性和可维护性,我们需要将数据请求与数据处理明确的区分开来。上面的写法,是完全没有区分开,当数据处理变得复杂时,也许我们自己都无法轻松维护自己的代码了。这也是模块化过程中,必须要掌握的一个重要技能,请一定重视。
从前面几篇文中的知识我们可以知道,当我们想要确保某代码在谁谁之后执行时,我们可以利用函数调用栈,将我们想要执行的代码放入回调函数中。
// 一个简单的封装
function want() {
console.log('这是你想要执行的代码');
}
function fn(want) {
console.log('这里表示执行了一大堆各种代码');
// 其他代码执行完毕,最后执行回调函数
want && want();
}
fn(want);
利用回调函数封装,是我们在初学JavaScript时常常会使用的技能。
确保我们想要的代码压后执行,除了利用函数调用栈的执行顺序之外,我们还可以利用队列机制。
function want() {
console.log('这是你想要执行的代码');
}
function fn(want) {
// 将想要执行的代码放入队列中,根据事件循环的机制,我们就不用非得将它放到最后面了,由你自由选择
want && setTimeout(want, 0);
console.log('这里表示执行了一大堆各种代码');
}
fn(want);
与setTimeout类似,Promise也可以认为是一种任务分发器,它将任务分配到Promise队列中,通常的流程是我们会首先发起一个请求,然后等待(等待时间我们无法确定)并处理请求结果。
简单的用法大概如下:
var tag = true;
var p = new Promise(function(resolve, reject) {
if (tag) {
resolve('tag is true');
} else {
reject('tag is false');
}
})
p.then(function(result) {
console.log(result);
})
.catch(function(err) {
console.log(err);
})
运行这段代码,并通过修改tag
的值来感受一下运行结果的不同。
然后接下来我们来了解Promise的相关基础知识。
new Promise
表示创建一个Promise实例对象Promise
函数中的第一个参数为一个回调函数,我们也可以称之为executor
。通常情况下,在这个函数中,我们将会执行发起请求操作,并修改结果的状态值。- 请求结果有三种状态,他们分别是
pending
(等待中,表示还没有得到结果),resolved
(得到了我们想要的结果,可以继续执行),rejected
(得到了错误的,或者不是我们期望的结果,将会拒绝继续执行。)。请求结果的默认状态为pending
,在executor
函数中,我们可以分别使用resolve
与reject
将状态修改为对应的resolved
与rejected
。resolve、reject
是executor
函数的两个参数。他们能够将请求结果的具体数据传递出去。 - Promise实例拥有的
then
方法,用来处理当请求结果的状态变成resolved
时的逻辑。then
的第一个参数也为一个回调函数,该函数的参数则是resolve
传递出来的数据。在上面的例子中,result = tag is true
。 - Promise实例拥有的
catch
方法,用来处理当请求结果的状态变成rejected
时的逻辑。catch
的第一个参数也为一个回调函数,该函数的参数则是reject
传递出来的数据。在上面的例子中,err = tag is false
。
下面通过几个简单的例子来感受一下Promise的用法。
例子1:
function fn(num) {
// 创建一个Promise实例
return new Promise(function(resolve, reject) {
if (typeof num == 'number') {
// 修改结果状态值为resolved
resolve();
} else {
// 修改结果状态值为rejected
reject();
}
}).then(function() {
console.log('参数是一个number值');
}).catch(function() {
console.log('参数不是一个number值');
})
}
// 修改参数的类型观察输出结果
fn('12');
// 注意观察该语句的执行顺序
console.log('next code');
例子2:
function fn(num) {
return new Promise(function(resolve, reject) {
// 模拟一个请求行为,2s以后得到结果
setTimeout(function() {
if (typeof num == 'number') {
resolve(num)
} else {
var err = num + ' is not a number.'
reject(err);
}
}, 2000);
})
.then(function(resp) {
console.log(resp);
})
.catch(function(err) {
console.log(err);
})
}
// 修改传入的参数类型观察结果变化
fn('abc');
// 注意观察该语句的执行顺序
console.log('next code');
因为fn函数运行的结果是返回的一个Promise对象,因此我们也可以将上面的例子修改如下:
function fn(num) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
if (typeof num == 'number') {
resolve(num)
} else {
var err = num + ' is not a number.'
reject(err);
}
}, 2000);
})
}
fn('abc')
.then(function(resp) {
console.log(resp);
})
.catch(function(err) {
console.log(err);
})
// 注意观察该语句的执行顺序
console.log('next code');
then
方法可以接收两个参数,第一个参数用来处理resolved
状态的逻辑,第二个参数用来处于rejected
状态的逻辑。
// 修改上面例子中的部分代码
fn('abc')
.then(function(resp) {
console.log(resp);
}, function(err) {
console.log(err)
})
因此catch
方法其实与下面的写法等价。
fn('abc').then(null, function(err) {
console.log(err)
})
then
方法因为返回的仍然是一个Promise实例对象,因此then方法可以嵌套使用,在这个过程中,通过在内部函数末尾return
的方式,能够将数据持续往后传递。下面的例子中,注意观察数据传递过程中的变化。
function fn(num) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
if (typeof num == 'number') {
resolve(num)
} else {
var err = num + ' is not a number.'
reject(err);
}
}, 2000);
})
}
fn(20)
.then(function(result) {
console.log(result); //20
return result + 1;
})
.then(function(result) {
console.log(result); // 21
return result + 1;
})
.then(function(result) {
console.log(result); // 22
return result + 1;
})
.then(function(result) {
console.log(result); // 23
})
.then(function(result) {
console.log(result); // undefined
})
// 注意观察该语句的执行顺序
console.log('next code');
OK,了解了这些基础知识之后,我们再回过头来看看最开始我们提到过的ajax
的例子。我们可以进行一个简单的封装。详细见代码。
var url = 'https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-02-26/2017-06-10';
// 封装一个get请求的方法
function getJSON(url) {
return new Promise(function(resolve, reject) {
// 利用ajax发送一个请求
var XHR = new XMLHttpRequest();
XHR.open('GET', url, true);
XHR.send();
// 等待结果
XHR.onreadystatechange = function() {
if (XHR.readyState == 4) {
if (XHR.status == 200) {
try {
var response = JSON.parse(XHR.responseText);
// 得到正确的结果修改状态并将数据传递出去
resolve(response);
} catch (e) {
reject(e);
}
} else {
// 得到错误结果并抛出异常
reject(new Error(XHR.statusText));
}
}
}
})
}
// 封装好之后,使用就很简单了
getJSON(url).then(function(resp) {
console.log(resp);
// 之后就是处理数据的具体逻辑
});
现在所有的库几乎都将ajax请求利用Promise进行了封装,当然也包括非常常用的jQuery,因此我们在使用jQuery等库中的ajax请求时,都可以利用Promise来让我们的代码更加优雅和简单。
$.get(url).then(function(resp) {
// ... 处理success的结果
})
.catch(function(err) {
// ...
})
Promise.all
当有一个ajax请求,它的参数需要另外2个甚至更多请求都有返回结果之后才能确定,那么这个时候,就需要用到Promise.all来帮助我们应对这个场景。
Promise.all
接收一个Promise对象组成的数组作为参数,当这个数组所有的Promise对象状态都变成resolved或者rejected的时候,它才会去调用then方法。
var url = 'https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-02-26/2017-06-10';
var url1 = 'https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-03-26/2017-06-10';
function renderAll() {
return Promise.all([getJSON(url), getJSON(url1)]);
}
renderAll().then(function(value) {
// 建议大家在浏览器中看看这里的value值
console.log(value);
})
Promise.race
与Promise.all相似的是,Promise.race都是以一个Promise对象组成的数组作为参数,不同的是,只要当数组中的其中一个Promsie状态变成resolved或者rejected时,就可以调用.then方法了。而传递给then方法的值也会有所不同,大家可以再浏览器中运行下面的例子与上面的例子进行对比。
function renderRace() {
return Promise.race([getJSON(url), getJSON(url1)]);
}
renderRace().then(function(value) {
console.log(value);
})