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函数中,我们可以分别使用resolvereject将状态修改为对应的resolvedrejectedresolve、rejectexecutor函数的两个参数。他们能够将请求结果的具体数据传递出去。
  • 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);
})

results matching ""

    No results matching ""