深入理解 JavaScript 异步系列(2)—— jquery的解决

日期:2017-08-23 点击:

本节内容概述

  • 传统的$.ajax
  • 1.5 版本之后的$.ajax
  • 改进之后的好处
  • 和后来的Promise的关系
  • 如何实现的?

传统的$.ajax

先来一段最常见的$.ajax的代码,当然是使用万恶的callback方式

 
var ajax = $.ajax({
    url: 'data.json',
    success: function () {
        console.log('success')
    },
    error: function () {
        console.log('error')
    }
})

console.log(ajax) // 返回一个 XHR 对象
 

至于这么做会产生什么样子的诟病,我想大家应该都很明白了。不明白的自己私下去查,但是你也可以继续往下看,你只需要记住这样做很不好就是了,要不然 jquery 也不会再后面进行改进

1.5 版本之后的$.ajax

但是从v1.5开始,以上代码就可以这样写了:可以链式的执行done或者fail方法

 
var ajax = $.ajax('data.json')
ajax.done(function () {
        console.log('success 1')
    })
    .fail(function () {
        console.log('error')
    })
    .done(function () {
         console.log('success 2')
    })

console.log(ajax) // 返回一个 deferred 对象
 

大家注意看以上两段代码中都有一个console.log(ajax),但是返回值是完全不一样的。

  • v1.5之前,返回的是一个XHR对象,这个对象不可能有done或者fail的方法的
  • v1.5开始,返回一个deferred对象,这个对象就带有donefail的方法,并且是等着请求返回之后再去调用

改进之后的好处

这是一个标志性的改造,不管这个概念是谁最先提出的,它在 jquery 中首先大量使用并让全球开发者都知道原来 ajax 请求还可以这样写。这为以后的Promise标准制定提供了很大意义的参考,你可以以为这就是后面Promise的原型。

记住一句话————虽然 JS 是异步执行的语言,但是人的思维是同步的————因此,开发者总是在寻求如何使用逻辑上看似同步的代码来完成 JS 的异步请求。而 jquery 的这一次更新,让开发者在一定程度上得到了这样的好处。

之前无论是什么操作,我都需要一股脑写到callback中,现在不用了。现在成功了就写到done中,失败了就写到fail中,如果成功了有多个步骤的操作,那我就写很多个done,然后链式连接起来就 OK 了。

和后来的Promise的关系

以上的这段代码,我们还可以这样写。即不用donefail函数,而是用then函数。then函数的第一个参数是成功之后执行的函数(即之前的done),第二个参数是失败之后执行的函数(即之前的fail)。而且then函数还可以链式连接。

 
var ajax = $.ajax('data.json')
ajax.then(function () {
        console.log('success 1')
    }, function () {
        console.log('error 1')
    })
    .then(function () {
        console.log('success 2')
    }, function () {
        console.log('error 2')
    })
 

如果你对现在 ES6 的Promise有了解,应该能看出其中的相似之处。不了解也没关系,你只需要知道它已经和Promise比较接近了。后面马上会去讲Promise

如何实现的?

明眼人都知道,jquery 不可能改变异步操作需要callback的本质,它只不过是自己定义了一些特殊的 API,并对异步操作的callback进行了封装而已。

那么 jquery 是如何实现这一步的呢?请听下回分解!

 

第二部分,jQuery deferred

上一节讲到 jquery v1.5 版本开始,$.ajax可以使用类似当前Promisethen函数以及链式操作。那么它到底是如何实现的呢?在此之前所用到的callback在这其中又起到了什么作用?本节给出答案

本节使用的代码参见这里

本节内容概述

  • 写一个传统的异步操作
  • 使用$.Deferred封装
  • 应用then方法
  • 有什么问题?

写一个传统的异步操作

给出一段非常简单的异步操作代码,使用setTimeout函数。

 
var wait = function () {
    var task = function () {
        console.log('执行完成')
    }
    setTimeout(task, 2000)
}
wait()
 

以上这些代码执行的结果大家应该都比较明确了,即 2s 之后打印出执行完成但是我如果再加一个需求 ———— 要在执行完成之后进行某些特别复杂的操作,代码可能会很多,而且分好几个步骤 ———— 那该怎么办? 大家思考一下!

如果你不看下面的内容,而且目前还没有Promise的这个思维,那估计你会说:直接在task函数中写就是了!不过相信你看完下面的内容之后,会放弃你现在的想法。

使用$.Deferred封装

好,接下来我们让刚才简单的几行代码变得更加复杂。为何要变得更加复杂?是因为让以后更加复杂的地方变得简单。这里我们使用了 jquery 的$.Deferred,至于这个是个什么鬼,大家先不用关心,只需要知道$.Deferred()会返回一个deferred对象,先看代码,deferred对象的作用我们会面会说。

 
function waitHandle() {
    var dtd = $.Deferred()  // 创建一个 deferred 对象

    var wait = function (dtd) {  // 要求传入一个 deferred 对象
        var task = function () {
            console.log('执行完成')
            dtd.resolve()  // 表示异步任务已经完成
        }
        setTimeout(task, 2000)
        return dtd  // 要求返回 deferred 对象
    }

    // 注意,这里一定要有返回值
    return wait(dtd)
}
 

以上代码中,又使用一个waitHandle方法对wait方法进行再次的封装。waitHandle内部代码,我们分步骤来分析。跟着我的节奏慢慢来,保证你不会乱。

  • 使用var dtd = $.Deferred()创建deferred对象。通过上一节我们知道,一个deferred对象会有done failthen方法(不明白的去看上一节)
  • 重新定义wait函数,但是:第一,要传入一个deferred对象(dtd参数);第二,当task函数(即callback)执行完成之后,要执行dtd.resolve()告诉传入的deferred对象,革命已经成功。第三;将这个deferred对象返回。
  • 返回wait(dtd)的执行结果。因为wait函数中返回的是一个deferred对象(dtd参数),因此wait(dtd)返回的就是dtd————如果你感觉这里很乱,没关系,慢慢捋,一行一行看,相信两三分钟就能捋顺!

最后总结一下,waitHandle函数最终return wait(dtd)即最终返回dtd(一个deferred)对象。针对一个deferred对象,它有done failthen方法(上一节说过),它还有resolve()方法(其实和resolve相对的还有一个reject方法,后面会提到)

应用then方法

接着上面的代码继续写

 
var w = waitHandle()
w.then(function () {
    console.log('ok 1')
}, function () {
    console.log('err 1')
}).then(function () {
    console.log('ok 2')
}, function () {
    console.log('err 2')
})
 

上面已经说过,waitHandle函数最终返回一个deferred对象,而deferred对象具有done fail then方法,现在我们正在使用的是then方法。至于then方法的作用,我们上一节已经讲过了,不明白的同学抓紧回去补课。

执行这段代码,我们打印出来以下结果。可以将结果对标以下代码时哪一行。

执行完成
ok 1
ok 2

此时,你再回头想想我刚才说提出的需求(要在执行完成之后进行某些特别复杂的操作,代码可能会很多,而且分好几个步骤),是不是有更好的解决方案了?

有同学肯定发现了,代码中console.log('err 1')console.log('err 2')什么时候会执行呢 ———— 你自己把waitHandle函数中的dtd.resolve()改成dtd.reject()试一下就知道了。

  • dtd.resolve() 表示革命已经成功,会触发then中第一个参数(函数)的执行,
  • dtd.reject() 表示革命失败了,会触发then中第二个参数(函数)执行

有什么问题?

总结一下一个deferred对象具有的函数属性,并分为两组:

  • dtd.resolve dtd.reject
  • dtd.then dtd.done dtd.fail

我为何要分成两组 ———— 这两组函数,从设计到执行之后的效果是完全不一样的。第一组是主动触发用来改变状态(成功或者失败),第二组是状态变化之后才会触发的监听函数。

既然是完全不同的两组函数,就应该彻底的分开,否则很容易出现问题。例如,你在刚才执行代码的最后加上这么一行试试。

w.reject()

那么如何解决这一个问题?请听下回分解!

 

第三部分,jQuery promise

上一节通过一些代码演示,知道了 jquery 的deferred对象是解决了异步中callback函数的问题,但是

本节使用的代码参见这里

本节内容概述

  • 返回promise
  • 返回promise的好处
  • promise 的概念

返回promise

我们对上一节的的代码做一点小小的改动,只改动了一行,下面注释。

 
function waitHandle() {
    var dtd = $.Deferred()
    var wait = function (dtd) {
        var task = function () {
            console.log('执行完成')
            dtd.resolve()
        }
        setTimeout(task, 2000)
        return dtd.promise()  // 注意,这里返回的是 primise 而不是直接返回 deferred 对象
    }
    return wait(dtd)
}

var w = waitHandle() // 经过上面的改动,w 接收的就是一个 promise 对象
$.when(w)
 .then(function () {
    console.log('ok 1')
 })
 .then(function () {
    console.log('ok 2')
 })
 

改动的一行在这里return dtd.promise(),之前是return dtddtd是一个deferred对象,而dtd.promise就是一个promise对象。

promise对象和deferred对象最重要的区别,记住了————promise对象相比于deferred对象,缺少了.resolve.reject这俩函数属性。这么一来,可就完全不一样了。

上一节我们提到一个问题,就是在程序的最后一行加一句w.reject()会导致乱套,你现在再在最后一行加w.reject()试试 ———— 保证乱套不了 ———— 而是你的程序不能执行,直接报错。因为,wpromise对象,不具备.reject属性。

返回promise的好处

  • waitHandle函数内部,使用dtd.resolve()来该表状态,做主动的修改操作
  • waitHandle最终返回promise对象,只能去被动监听变化(then函数),而不能去主动修改操作

一个“主动”一个“被动”,完全分开了。

推荐动态

最新动态

LATEST NEWS

填写您的项目需求给我们。

*请认真填写需求信息,我们会在24小时内与您取得联系。

启邦互动

品牌官网设计|网站建设|网站设计

深圳·龙岗区

大芬油画村黄江广场2楼202

电话:0755-2868 4353

邮箱:service@sz-qibang.com

© 2017深圳市启邦互动科技有限公司 版权所有 粤ICP备13054229号-1 网站地图     付款方式