资讯专栏INFORMATION COLUMN

通过 ES6 Promise 和 jQuery Deferred 的异同学习 Promise

Yujiaao / 2103人阅读

摘要:和和都有和,但是略有不同。实际上返回的是一个对象。和添加的回调,添加的回调。所以在调用成功的情况下执行添加的回调,调用失败时执行添加的回调。,产生对象并,产生对象并,然后继续处理,的语法糖,和的差不多但不同。

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,而是 donefailalways。除了 $.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)`
> `executor`、`resolve` 和 `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

Deferred Object | jQuery Documentation

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/85953.html

相关文章

  • 学习promise编写使用

    摘要:实现是中的一种异步编程实现方式,中异步编程主要是指浏览器事件处理,等,通过传入回调函数来实现控制反转。对象符合编程规范,目的是为异步编程提供统一接口,它最大的优点就是避免了回调金字塔。 promise实现 Promise是Javascript中的一种异步编程实现方式,js中异步编程主要是指浏览器DOM事件处理,setTimeout/setInterval,ajax等,通过传入回调函数来...

    aaron 评论0 收藏0
  • jQuery源码解析Deferred异步对象

    摘要:回调队列对象,用于构建易于操作的回调函数集合,在操作完成后进行执行。对象对象,用于管理回调函数的多用途列表。如果传入一个延迟对象,则返回该对象的对象,可以继续绑定其余回调,在执行结束状态之后也同时调用其回调函数。 在工作中我们可能会把jQuery选择做自己项目的基础库,因为其提供了简便的DOM选择器以及封装了很多实用的方法,比如$.ajax(),它使得我们不用操作xhr和xdr对象,直...

    Coding01 评论0 收藏0
  • 一篇文章带你尝试拿下js异步

    摘要:单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。这决定了它只能是单线程,否则会带来很复杂的同步问题。小结本身是单线程的,并没有异步的特性。当异步函数执行时,回调函数会被压入这个队列。 走在前端的大道上 本篇将自己读过的相关 js异步 的文章中,对自己有启发的章节片段总结在这(会对原文进行删改),会不断丰富提炼总结更新。 概念 JS 是单线程的语言。 单线程就意味着...

    MartinDai 评论0 收藏0
  • 传统 Ajax 已死,Fetch 永生

    摘要:结果证明,对于以上浏览器,在生产环境使用是可行的。后面可以跟对象,表示等待才会继续向下执行,如果被或抛出异常则会被外面的捕获。,,都是现在和未来解决异步的标准做法,可以完美搭配使用。这也是使用标准一大好处。只允许外部传入成功或失败后的回调。 showImg(https://cloud.githubusercontent.com/assets/948896/10188666/bc9a53...

    fai1017 评论0 收藏0
  • ES6(一) —— 异步编程解决办法[从回调函数到promise,generator,async]

    摘要:回调函数这是最原始的一种异步解决方法。从的对象演化而来对象是提出的一种对异步编程的解决方案,但它不是新的语法,而是一种新的写法,允许将回调函数的嵌套改成链式调用。 一、前言 异步编程对JavaScript来说非常重要,因为JavaScript的语言环境是单线程的,如果没有异步编程将变得非常可怕,估计根本无法使用。这篇文章就来总结一下从最原始的回调函数到现在的ES6、ES7的新方法。 文...

    greatwhole 评论0 收藏0

发表评论

0条评论

Yujiaao

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<