把ajax包装成promise的形式

形式一

为了体验promise的原理,我打算自己把ajax包装成promise的形式。主要希望实现下列功能:

// 1.使用success和error进行链式调用,并且可以在后面加上无限个
promise.get(myUrl).success(successCallback1).error(errorCallback1).success(successCallback2).error(errorCallback2).error(errorCallback3).success(successCallback3);

// 2.支持同时调用多个myUrl,这个时候需要最后的http请求返回之后才执行回调。
promise.get(myUrl1).success(successCallback1).get(myUrl2).error(errorCallback1).get(myUrl3).error(errorCallback2).success(successCallback1);

// 3.支持post和jsonp请求。

对于ajax我选用jq的ajax,但是尽量不使用jq的deferred对象。

功能一

初看功能一,我们有如下思路

  1. 建立一个构造函数,它的原型方法有get(发出get请求),success(挂载成功函数回调),error(挂载失败函数回调);
  2. 这个构造函数有一个原型对象,来分别储存成功和失败的回调函数。
  3. get请求里面发出ajax,并依次执行原型对象的成功和失败回调函数。
let Promise = function() {
    this.eventName = {
        success: [],
        error: []
    };
};

Promise.prototype.success = function(cb) {
    this.eventName.success.push(cb);
    return this;
};

Promise.prototype.error = function(cb) {
    this.eventName.error.push(cb);
    return this;
};

Promise.prototype.get = function(url) {
    let that = this;
    $.ajax({
        url: url,
        type: 'get',
        timeout: 5000,
        success: function (data, status) {
            let successList = that.eventName.success;
            if(successList || successList.length) {
                for(let i = 0; i < successList.length; i++) {
                    successList[i](data, status);
                }
            }
        },
        error: function (err, status) {
            let errorList = that.eventName.error;
            if(errorList || errorList.length) {
                for(let i = 0; i < errorList.length; i++) {
                    errorList[i](err, status);
                }
            }
        }
    });
    return this;
};

然后我们可以执行:

let successCallback1 = (data, status) => {
    console.log(data);
}

let successCallback2 = (data, status) => {
    console.log(data);
}

let errorCallback1 = (err, status) => {
    console.log(data);
}

let errorCallback2 = (err, status) => {
    console.log(data);
}

let url = 'xxxxxxxxxx';
let promise = new Promise();
promise.success(successCallback1)
       .error(errorCallback1)
       .error(errorCallback2)
       .success(successCallback2)
       .get(url);

但是这样有一个缺点,就是get方法只能在最后调用,不能提到前面来。

怎么提到前面来变成下面的调用形式呢?

promise.get(url)
       .success(successCallback1)
       .error(errorCallback1)
       .error(errorCallback2)
       .success(successCallback2);

思考可以用setTimeout,测试代码如下:

var Haha = function() {
    this.a = [];
};

Haha.prototype.go = function() {
    setTimeout(() => {
        this.a.push(1);
        let that = this;
        setTimeout(() => {
            that.a.push(2);
        });
    });
    return this;
};

Haha.prototype.push = function(ele) {
    this.a.push(ele);
    return this;
};

var yaya = new Haha();

yaya.go().push(3).push(4);
// 过一段时间再打印yaya.a,可以看到输出[3, 4, 1, 2]

所以类似的,我们可以把get方法修改如下:

Promise.prototype.get = function() {
    setTimeout(() => {
        let that = this;
        $.ajax({
            url: myUrl,
            type: 'get',
            timeout: 5000,
            success: function (data, status) {
                let successList = that.eventName.success;
                if(successList || successList.length) {
                    for(let i = 0; i < successList.length; i++) {
                        successList[i](data, status);
                    }
                }
            },
            error: function (err, status) {
                let errorList = that.eventName.error;
                if(errorList || errorList.length) {
                    for(let i = 0; i < errorList.length; i++) {
                        errorList[i](err, status);
                    }
                }
            }
        });
    }, 0);
    return this;
}

 目测可行,之后找个时间测试一下。

形式二

概述 

为了体验promise的原理,我打算自己把ajax包装成promise的形式。主要希望实现下列功能:

 

// 1.使用success和error进行链式调用,并且可以在后面加上无限个
promise.get(myUrl).success(successCallback1).error(errorCallback1).success(successCallback2).error(errorCallback2).error(errorCallback3).success(successCallback3);

// 2.支持同时调用多个myUrl,这个时候需要最后的http请求返回之后才执行回调。
promise.get(myUrl1).success(successCallback1).get(myUrl2).error(errorCallback1).get(myUrl3).error(errorCallback2).success(successCallback1);

// 3.支持post和jsonp请求。

对于ajax我选用jq的ajax,但是尽量不使用jq的deferred对象。

测试用ajax 

因为用实际ajax接口非常不方便测试,所以我用setTimeout模拟了一个ajax方法进行测试用。

值得一提的是,如果在node端,建议使用setImmediate代替setTimeout会有更好的性能。目前浏览器端只有IE支持setImmediate。

// 模拟ajax
let mockAjax = ({ url, type, success, error }) => {
    let data = url + type, err = url + type, status;
    // 随机执行success或者error
    setTimeout(() => {
        let rand = Math.random() > 0.1;
        if(rand) {
            status = 1;
            if(typeof success == 'function') success(data, status);
        } else {
            status = 0;
            if(typeof error == 'function') error(err, status);
        }
    });
}

mockAjax随机进行成功或者失败返回,可以更改上面的0.1来调高或者调低相关概率。

需要说明的是,最好在chrome的开发者工具下运行上面的代码,其它浏览器可能不支持参数解构

使用mockAjax进行测试

// 模拟ajax
let mockAjax = ({ url, type, success, error }) => {
    let data = url + type, err = url + type, status;
    // 随机执行success或者error
    setTimeout(() => {
        let rand = Math.random() > 0.5;
        if(rand) {
            status = 1;
            if(typeof success == 'function') success(data, status);
        } else {
            status = 0;
            if(typeof error == 'function') error(err, status);
        }
    });
}

let Promise = function() {
    this.eventName = {
        success: [],
        error: []
    };
};

Promise.prototype.success = function(cb) {
    this.eventName.success.push(cb);
    return this;
};

Promise.prototype.error = function(cb) {
    this.eventName.error.push(cb);
    return this;
};

Promise.prototype.get = function(url) {
    let that = this;
    setTimeout(() => {
        mockAjax({
            url: url,
            type: 'get',
            success: function (data, status) {
                let successList = that.eventName.success;
                if(successList || successList.length) {
                    for(let i = 0; i < successList.length; i++) {
                        successList[i](data, status);
                    }
                }
            },
            error: function (err, status) {
                let errorList = that.eventName.error;
                if(errorList || errorList.length) {
                    for(let i = 0; i < errorList.length; i++) {
                        errorList[i](err, status);
                    }
                }
            }
        });
    });

    return this;
};

// test===================
let successCallback = (message) => (data, status) => {
    console.log(data + '成功回调' + message);
}
let errorCallback = (message) => (err, status) => {
    console.log(err + '失败回调' + message);
}

let testPromise = new Promise();
testPromise.get('url1').success(successCallback(1)).success(successCallback(2)).error(errorCallback(1)).error(errorCallback(2));

可以看到浏览器输出如下:

// 成功情况
url1get成功回调1
url1get成功回调2
// 失败情况
url1get失败回调1
url1get失败回调2

多异步协作

多异步情况下会返回什么呢?

testPromise.get('url1').success(successCallback(1)).success(successCallback(2)).error(errorCallback(1)).error(errorCallback(2)).get('url2').success(successCallback(3)).error(errorCallback(3)).get('url3');

输出如下:

url1get成功回调1
url1get成功回调2
url1get成功回调3
url2get成功回调1
url2get成功回调2
url2get成功回调3
url3get失败回调1
url3get失败回调2
url3get失败回调3

冷静分析,在url1返回的时候,url2和url3没有返回,此时对url1返回的数据执行了三个成功回调。

但是更多情况下,我们需要url1, url2, url3全部返回之后再执行成功回调或者失败回调。

接下来实现

为了体验promise的原理,我打算自己把ajax包装成promise的形式。主要希望实现下列功能:

 

// 1.使用success和error进行链式调用,并且可以在后面加上无限个
promise.get(myUrl).success(successCallback1).error(errorCallback1).success(successCallback2).error(errorCallback2).error(errorCallback3).success(successCallback3);

// 2.支持同时调用多个myUrl,这个时候需要最后的http请求返回之后才执行回调。
promise.get(myUrl1).success(successCallback1).get(myUrl2).error(errorCallback1).get(myUrl3).error(errorCallback2).success(successCallback1);

// 3.支持post和jsonp请求。

对于ajax我选用jq的ajax,但是尽量不使用jq的deferred对象。

实现功能2:多异步情形。

多异步情形

在多异步情形下,成功回调和失败回调的处理方式有如下情况:

  1. 成功回调接收所有参数,失败回调接收单一参数,只要失败,成功回调不执行,只执行相应失败回调。
  2. 成功回调接收所有参数,失败回调接收单一参数,就算失败,也执行成功回调。
  3. 成功回调接收所有参数,失败回调接收所有参数,就算失败,也执行成功回调。
  4. 等等。

上面的情况都可以实现,但是只有情况1比较符合实际业务,所以我们只实现情况1.

实现的方法是:我们引入一个哨兵变量,并且把返回的数据按顺序储存起来,用哨兵变量判断出最后一个成功返回后,表示数据全部返回,然后执行回调函数。

代码如下:

// 模拟ajax
let mockAjax = ({ url, type, success, error }) => {
    let data = url + type, err = url + type, status;
    // 随机执行success或者error
    setTimeout(() => {
        let rand = Math.random() > 0.1;
        if(rand) {
            status = 1;
            if(typeof success == 'function') success(data, status);
        } else {
            status = 0;
            if(typeof error == 'function') error(err, status);
        }
    });
}

let Promise = function() {
    this.eventName = {
        success: [],
        error: []
    };
    // 返回数据的结果集合
    this.result = [];
    // 哨兵变量
    this.sum = 0;
    this.count = 0;
};

Promise.prototype.success = function(cb) {
    this.eventName.success.push(cb);
    return this;
};

Promise.prototype.error = function(cb) {
    this.eventName.error.push(cb);
    return this;
};

Promise.prototype.get = function(url) {
    // 返回数据的储存顺序
    let index = this.sum;
    this.sum ++;
    setTimeout(() => {
        let that = this;
        mockAjax({
            url: url,
            type: 'get',
            success: function (data, status) {
                if(that.sum !== 0) {
                    that.result[index] = data;
                    that.count ++;
                    if(that.count == that.sum) {
                        let successList = that.eventName.success;
                        if(successList || successList.length) {
                            for(let i = 0; i < successList.length; i++) {
                                successList[i](that.result, status);
                            }
                        }
                        // 初始化
                        that.result.length = 0;
                        that.sum = 0;
                        that.count = 0;
                    }
                }
            },
            error: function (err, status) {
                that.result.length = 0;
                that.sum = 0;
                that.count = 0;
                let errorList = that.eventName.error;
                if(errorList || errorList.length) {
                    for(let i = 0; i < errorList.length; i++) {
                        errorList[i](err, status);
                    }
                }
            }
        });
    });
    return this;
}

// test===================
let successCallback = (message) => (result, status) => {
    result.map(ele => {
        console.log(ele + '成功回调' + message);
    });
}
let errorCallback = (message) => (err, status) => {
    console.log(err + '失败回调' + message);
}

let testPromise = new Promise();
testPromise.get('url1').success(successCallback(1)).success(successCallback(2)).error(errorCallback(1)).error(errorCallback(2)).get('url2').success(successCallback(3)).error(errorCallback(3)).get('url3');

测试输出结果如下:

// 成功情况
url1get成功回调1
url2get成功回调1
url3get成功回调1
url1get成功回调2
url2get成功回调2
url3get成功回调2
url1get成功回调3
url2get成功回调3
url3get成功回调3

// 失败情况
url1get失败回调1
url1get失败回调2
url1get失败回调3

可以看到,成功情况,对返回的三个数据依次执行成功123;失败情况,只对失败的url1执行了失败回调123。 

上一篇:【SpringMVC 从 0 开始】域对象共享数据


下一篇:maven 使用记录之修改 maven默认jdk版本