zoukankan      html  css  js  c++  java
  • 通过 ES6 Promise 和 jQuery Deferred 的异同学习 Promise

    Deferred 和 Promise

    ES6 和 jQuery 都有 Deffered 和 Promise,但是略有不同。不过它们的作用可以简单的用两句话来描述

    • Deffered 触发 resolve 或 reject
    • Promise 中申明 resolve 或 reject 后应该做什么(回调)

    在 jQuery 中

    var deferred = $.Deferred();
    var promise = deferred.promise();

    在 ES6 中

    var deferred = Promise.defer();
    var promise= defered.promise;

    MDN 宣布 Deferred 在 Gecko 30 中被申明为过期,不应该再使用,而应该用 new Promise() 来代替。关于 new Promise() 将在后面说明。

    jQuery 的 Deferred/Promise

    jQuery 中最常用的 Promise 对象是 $.ajax() 返回的,最常用的方法不是 then,而是 donefail 和 always。除了 $.ajax() 外,jQuery 也提供了 $.get()$.post() 和 $.getJSON() 等简化 Ajax 调用,它们返回的和 $.ajax() 的返回值一样,是个 Promise 对象。

    实际上 $.ajax() 返回的是一个 jqXHR 对象。但 jqXHR 实现了 jQuery 的 Promise 接口,所以也是一个 Promise 对象。

    done()fail() 和 always()

    done() 添加 deferred.resolve() 的回调,fail() 添加 deferred.reject() 的回调。所以在 Ajax 调用成功的情况下执行 done() 添加的回调,调用失败时执行 fail() 添加的回调。但不管成功与否,都会执行 always() 添加的回调。

    这里 done()fail() 和 always() 都是以类似事件的方式添加回调,也就意味着,不管执行多次次 done()fail() 或 always(),它们添加的若干回调都会在符合的条件下依次执行。

    一般情况下会这样执行 Ajax

    // 禁用按钮以避免重复提交
    $("#theButton").prop({
        disabled: true
    });
    
    // 调用 Ajax 提交数据,假设返回的是 JSON 数据
    var jqxhr = $.ajax("do/example", {
        type: "post",
        dataType: "json",
        data: getFormData()
    });
    
    jqxhr.done(function(jsonObject) {
        // Ajax 调用成功
        console.log("success with data", jsonObject);
    }).fail(function() {
        // Ajax 调用失败
        console.log("failed")
    }).always(function() {
        // 不管成功与否,都会执行,取消按钮的禁用状态
        $("#theButton").prop({
            disabled: false
        });
    });

    上面是最普通最常用的用法,但是在一个项目中总是这么写 Ajax,有点累,稍微约定一下再封装一下就使用起来就会便捷得多。首先,假设我们定义返回的 JSON 是这样的格式:

    {
        "code": "int, 0 表示成功,其它值表示出错",
        "message": "string, 附加的消息,可选",
        "data": "object,附加的数据,可选
    }

    然后为项目公共类 app 定义一个 ajax 方法

    app.ajax = function(button, url, data) {
        if (button) {
            button.prop("disabled", true);
        }
    
        return $.ajax(url, {
            type: "post",
            dataType: "json",
            data: data
        }).done(function(json) [
            if (json.code !== 0) {
                showError(json.message || "操作发生错误");
            }
        }).fail(function() {
            showError("服务器错误,请稍后再试");
        }).always(function() {
            if (button) {
                button.prop("disabled", false);
            }
        });
    };
    
    // 调用
    app.ajax("do/example", getFormData()).done(function(json) {
        if (json.code === 0) {
            // 只需要处理正确的情况啦
        }
    });

    不过还是有点不爽,如果不需要判断 json.code === 0 就更好了。这个……可以自己用一个 Deferred 来处理:

    app.ajax = function(button, url, data) {
        if (button) {
            button.prop("disabled", true);
        }
    
        var deferred = $.Deferred();
        $.ajax(url, {
            type: "post",
            dataType: "json",
            data: data
        }).done(function(json) [
            if (json.code !== 0) {
                showError(json.message || "操作发生错误");
                deferred.reject();
            } else {
                deferred.resolve(json);
            }
        }).fail(function() {
            showError("服务器错误,请稍后再试");
            deferred.reject();
        }).always(function() {
            if (button) {
                button.prop("disabled", false);
            }
        });
        return deferred.promise();
    };
    
    // 调用
    app.ajax("do/example", getFormData()).done(function(json) {
        // json.code === 0 总是成立
        // 正常处理 json.data 就好
    });

    注意,这里已经不是直接返回 $.ajax() 的结果 jqXHR 对象了,返回的是新建 Deferred 对象的 promise 对象。

    复习了 Ajax,现在需要切入正题,找到 jQuery Promise 和 ES6 Promise 接近的地方——then()

    jQuery deferred.then()

    在 jQuery 1.8 以前(不含 1.8,比如 jQuery 1.7.2),deferred.then() 就是一个把 done() 和 fail() 放在一起的语法糖。jQuery 在 1.8 版本的时候修改了 deferred.then() 的行为,使 then() 的行为与 Promise 的 then() 相似。从 jQuery 的文档可以看到 1.8 版本的变化——干掉了 callback,换成了 filter:

    // version added: 1.5, removed: 1.8
    deferred.then( doneCallbacks, failCallbacks )
    
    // version added: 1.7, removed: 1.8
    deferred.then( doneCallbacks, failCallbacks [, progressCallbacks ] )
    
    // version added: 1.8
    deferred.then( doneFilter [, failFilter ] [, progressFilter ] )

    可以简单的把 callback 当作一个事件处理,值用于 callback 之后一般不会改变;而 filter 不同,一个值传入 filter 再从 filter 返回出来,可能已经变了。还是举个例子来说明

    var deferred = $.Deferred();
    var promise = deferred.promise();
    promise.then(function(v) {
        console.log(`then with ${v}`);
    }).done(function(v) {
        console.log(`done with ${v}`);
    });
    deferred.resolve("resolveData");

    在 jQuery 1.7.2 中的结果

    then with resolveData
    done with resolveData

    在 jQuery 1.8.0 中的结果

    then with resolveData
    done with undefined

    从上面来看,jQuery 的 deferred.then() 语义和 ES6 Promise.then() 语义基本一致。如果把上面的 app.ajax 换成 then() 实现会有助于对 ES6 Promise 的理解。

    app.ajax = function(button, url, data) {
        if (button) {
            button.prop("disabled", true);
        }
    
        return $.ajax(url, {
            type: "post",
            dataType: "json",
            data: data
        }).then(function(json) {
            if (json.code !== 0) {
                showError(json.message || "操作发生错误");
                return $.Deferred().reject().promise();
            } else {
                return $.Deferred().resolve(json).promise();
            }
        }, function() {
            showError("服务器错误,请稍后再试");
            deferred.reject();
        }).always(function() {
            if (button) {
                button.prop("disabled", false);
            }
        });
    };
    
    // 调用方式没变,用 done,也可以用 then
    app.ajax("do/example", getFormData()).done(function(json) {
        // json.code === 0 总是成立
        // 正常处理 json.data 就好
    });

    从 jQuery Promise 到 ES6 Promise

    上面的代码太长,提炼一下关键部分(示意,不能运行)

    var promise = $.ajax();
    promise.then(function(data) {
        // resolve
        return data.code
            ? new Promise().reject()
            : new Promise().resolve(data);
    
        // 如果没有错,就返回一个新的 promise,并使用 data 来 resolve,
        // 也可以直接返回 data,
        // 这样后面 then 的 resolve 部分才能收到数据
    }, function() {
        // rejected
    });
    
    // 调用阶段
    promise.then(function(data) {
        // 处理 data
    });

    也许你没注意到,其实上面的代码基本上就是 ES6 的 Promise 了。下面正式用 ES6 Promise 改写上面的示意代码

    var promise = new Promise(function(resolve, reject) {
        $.ajax().then(resolve, reject);
        // 上面这句没看懂?那换成这样你一定会懂
        // $.ajax().then(function(data) {
        //     resolve(data);
        // }, function() {
        //     reject();
        // });
    }).then(function(data) {
        return data.code
            ? Promise.reject()
            : Promise.resolve(data);
    
        // 这里 Promise.resolve(data) 同样可以直接替换为 data
    });
    
    // 调用没变
    promise.then(function(data) {
        // 处理 data
    });

    怎么样,差别不大吧。不知不觉就会 ES6 Promise 了!

    ES6 的 Promise

    上面已经把 ES6 的 Promise 带出来了,现在只需要把常用方法列出来作为参考即可

    注意,小写的 promise 表示 Promise 对象

    • new Promise(executor),产生一个新的 Promise 对象

      executor(resolve, reject)
      executorresolve 和 reject 均为函数,在 executor 中,正确处理调用 resolve() 返回数据,异常处理直接 throw new Error(...) 或调 reject() 返回数据。

    • Promise.resolve(data),产生 Promise 对象并 resolve

    • Promise.reject(),产生 Promise 对象并 reject

    • promise.then(onResolve, onReject),然后……继续处理

    • promise.catch(onReject)project.then(null, onReject) 的语法糖,和 jQuery 的 promise.fail() 差不多(但不同)。

    参考

      • ECMAScript 2015 Language Specification - ECMA-262 6th Edition
      • Deferred - Mozilla | MDN
      • Promise - Mozilla | MDN

    https://www.jianshu.com/p/07ce7190a097

    https://www.jianshu.com/p/38a85d1851bb

  • 相关阅读:
    C++中的模板编程
    C++中的抽象类
    C++中的多态
    C++中的继承
    操作符重载(二)
    操作符重载(一)
    C++中的类与对象模型
    [八省联考2018] 劈配 (网络流+二分)
    CF51F Caterpillar (边双+树形DP)
    CF36E Two Paths (欧拉回路+构造)
  • 原文地址:https://www.cnblogs.com/liu2020/p/12608893.html
Copyright © 2011-2022 走看看