深入理解 JavaScript 异步系列(2)—— jquery的解决方案
第一部分,jQuery-1.5 之后的 ajax
本地址 http://www.cnblogs.com/wangfupeng1988/p/6515779.html 未经允许不得转载~
$.ajax
这个函数各位应该都比较熟悉了,要完整的讲解 js 的异步操作,就必须先从$.ajax
这个方法说起。
想要学到全面的知识,大家就不要着急,跟随我的节奏来,并且相信我。我安排的内容,肯定都是有用的,对主题无用的东西,我不会拿来占用大家的时间。
本节内容概述
- 传统的
$.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
对象,这个对象就带有done
和fail
的方法,并且是等着请求返回之后再去调用
改进之后的好处
这是一个标志性的改造,不管这个概念是谁最先提出的,它在 jquery 中首先大量使用并让全球开发者都知道原来 ajax 请求还可以这样写。这为以后的Promise
标准制定提供了很大意义的参考,你可以以为这就是后面Promise
的原型。
记住一句话————虽然 JS 是异步执行的语言,但是人的思维是同步的————因此,开发者总是在寻求如何使用逻辑上看似同步的代码来完成 JS 的异步请求。而 jquery 的这一次更新,让开发者在一定程度上得到了这样的好处。
之前无论是什么操作,我都需要一股脑写到callback
中,现在不用了。现在成功了就写到done
中,失败了就写到fail
中,如果成功了有多个步骤的操作,那我就写很多个done
,然后链式连接起来就 OK 了。
和后来的Promise
的关系
以上的这段代码,我们还可以这样写。即不用done
和fail
函数,而是用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
可以使用类似当前Promise
的then
函数以及链式操作。那么它到底是如何实现的呢?在此之前所用到的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
fail
和then
方法(不明白的去看上一节) - 重新定义
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
fail
和then
方法(上一节说过),它还有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 dtd
。dtd
是一个deferred
对象,而dtd.promise
就是一个promise
对象。
promise
对象和deferred
对象最重要的区别,记住了————promise
对象相比于deferred
对象,缺少了.resolve
和.reject
这俩函数属性。这么一来,可就完全不一样了。
上一节我们提到一个问题,就是在程序的最后一行加一句w.reject()
会导致乱套,你现在再在最后一行加w.reject()
试试 ———— 保证乱套不了 ———— 而是你的程序不能执行,直接报错。因为,w
是promise
对象,不具备.reject
属性。
返回promise
的好处
上一节提到deferred
对象有两组属性函数,而且提到应该把这两组彻底分开。现在通过上面一行代码的改动,就分开了。
waitHandle
函数内部,使用dtd.resolve()
来该表状态,做主动的修改操作waitHandle
最终返回promise
对象,只能去被动监听变化(then
函数),而不能去主动修改操作
一个“主动”一个“被动”,完全分开了。
promise 的概念
jquery v1.5 版本发布时间距离现在(2017年初春)已经老早之前了,那会儿大家网页标配都是 jquery 。无论里面的deferred
和promise
这个概念和想法最早是哪位提出来的,但是最早展示给全世界开发者的是 jquery ,这算是Promise
这一概念最先的提出者。
其实本次课程主要是给大家分析 ES6 的Promise
Generator
和async-await
,但是为何要从 jquery 开始(大家现在用 jquery 越来越少)?就是要给大家展示一下这段历史的一些起点和发展的知识。有了这些基础,你再去接受最新的概念会非常容易,因为所有的东西都是从最初顺其自然发展进化而来的,我们要去用一个发展进化的眼光学习知识,而不是死记硬背。
求打赏
如果你看完了,感觉还不错,欢迎给我打赏 ———— 以激励我更多输出优质内容
最后,github地址是 https://github.com/wangfupeng1988/js-async-tutorial 欢迎 star 和 pr
-------
学习作者教程:《前端JS高级面试》《前端JS基础面试题》《React.js模拟大众点评webapp》《zepto设计与源码分析》《json2.js源码解读》