jQuery的deferred对象和promise对象
Posted 好好撸码
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了jQuery的deferred对象和promise对象相关的知识,希望对你有一定的参考价值。
在开发过程中一旦遇到需要耗时很长的操作时,既有异步操作也有同步操作,这些都不能让我们立即得到结果。于是我们会用到回调函数,事先规定一旦运行结束应该立即执行哪个方法。但是jQuery在回调函数方面比较弱,于是他们设计出deferred对象用来解决如何处理耗时操作的问题。
ajax的链式写法
jQuery的传统写法是这样的:
$.ajax({ url: 'example.php', success: function() { alert('Success!') }, error: function() { alert('Fail!') } })
$.ajax()接受一个对象参数,这个对象有两个方法:success指定成功以后的函数,error指定失败以后的函数。
$.ajax()完成后,如果jQuery版本低于1.5.0,返回的是XHR对象,无法进行链式操作;如果版本高于1.5.0则返回的是deferred对象,可以链式操作。
[
XHR对象是XMLHTTPRequest()构造函数实例化的对象;
javascript的链式操作即多个函数连在一起调用。
]
jQuery的新写法:
$.ajax('example.php') .done(function() { alert('Success!') }) .fail(function() { alert('Fail!') })
可以理解:done( )等同于success方法,fail( )等同于error方法。
指定同一操作的多个回调方法
deferred对象允许自由添加多个回调函数。
$.ajax('example.php') .done(function() { alert('Success!') }) .fail(function() { alert('Fail!') }) .done(function() { alert(Success Again!) })
回调函数可以添加任意多个,它们按照添加的顺序来执行。
为多个操作指定回调函数
deferred对象同样允许为多个事件指定一个回调函数。
$.when($.ajax('example1.php'), $.ajax('example2.php')) .done(function() { alert('Success!') }) .fail(function() { alert('Fail!') })
表示先执行两个ajax操作,如果都成功了那么就运行done()指定的回调函数,如有其中有一个失败了,那么就执行fail().
[
$.when( )接受deferred或者promise对象执行回调函数
]
普通操作的回调函数接口
deferred对象不光用于ajax,还可以用于其他任何一个操作。也就是说不管是ajax还是本地,异步还是同步,都可以使用deferred对象的各种方法执行回调函数。
举个例子:
const wait = () => { const tasks = () => { alert('Over!') } setTimeout(tasks, 5000) }
现在要对wait执行回调函数,应该怎么做呢?
根据上面提到的方法,可以使用$.when( )
$.when(wait()) .done(function() { alert('Success') }) .fail(function() { alert('Fail!') })
运行以后,效果却不是我们想要的。我们的预期是先弹窗Over,然后是Success,结果上面运行的结果却是首先弹窗Success,等待一段时间以后再弹窗Over.因此我们可以知道,这段代码其实是把我们的wait( )放到后面执行了。
为什么呢?
因为wait( )返回的并不是一个deferred对象!
于是要实现我们预期的效果就必须要对wait( )进行改写了:
//创建一个deferred对象
let dtd = $.Deferred() const wait = (dtd) => { const tasks = () => { alert('Over!')
// 改变deferred对象的执行状态 dtd.resolve() } setTimeout(tasks, 5000) return dtd }
改写之后,wait( )返回的已经是一个deferred对象了,于是我们可以按照最前面的方法进行链式操作了。
$.when(wait(dtd)) .done(function() { alert('Success') }) .fail(function() { alert('Fail!') })
deferred.resolve( )和deferred.reject( )
上面改写wait( )函数的时候,我们用到了dtd.resolve( ),那么这个resolve( )到底是干嘛的呢?
jQuery的规定是这样的:
deferred对象有三种状态:未完成(或者叫进行中),已完成和已失败
如果执行状态是已完成(resolve),deferred对象会立即调用done( )方法指定的回调函数、如果是已失败则调用fail( )指定的回调函数、如果状态是未完成那么就继续等待,或者执行progress( )方法指定的回调函数。
那么问题来了,最前面写ajax的时候也没有拿resolve( )出来说啊,为什么啊?
因为ajax操作中,deferred对象会根据ajax的返回结果,自己改变自身的执行状态。但是在我们的wait( )函数中,我们自己的函数并不知道什么时候是成功的,于是需要我们编码人员需要自己指定什么时候resolve( ).
于是dtd.resolve( )就很好理解了,即弹窗Over的时候同时改变deferred对象执行状态为已完成。
跟resolve( )类似的还有deferred.reject( ),就是将deferred对象的执行状态强行从“未完成”改成“已失败”。
let dtd = $.Deferred() //创建一个deferred对象 const wait = (dtd) => { const tasks = () => { alert('Over!') dtd.reject() // 改变deferred对象的执行状态 } setTimeout(tasks, 5000) return dtd } $.when(wait(dtd)) .done(function() { alert('Success') }) .fail(function() { alert('Fail!') })
可以预想到,这段代码的执行结果会是:先弹窗Over,再弹窗Fail。因为按照JS执行顺序,首先运行alert( ),然后再改变dtd的执行状态为已失败。
deferred.promise( )
上面的写法已经基本上达到了我们的预期,但是有一个问题,那就算我们声明的dtd是一个全局变量,如果它的执行状态在外部改变了的话,就会直接影响我们代码的运行结果:
//创建一个deferred对象
let dtd = $.Deferred() const wait = (dtd) => { const tasks = () => { alert('Over!') // 改变deferred对象的执行状态
dtd.reject() } setTimeout(tasks, 5000) return dtd } $.when(wait(dtd)) .done(function() { alert('Success') }) .fail(function() { alert('Fail!') }) dtd.resolve()
由于我在代码尾部加了一行resolve( ),这里我们就把dtd的执行状态给直接改成了已完成,结果可想而知done( )会立即执行,然后再执行setTimeout.
为了不让这种情况发生,jQuery又给我们提供了deferred.promise( )方法让我们在原来的deferred对象上返回一个promise对象。promise只开放done( ),fail( )这样的方法,屏蔽了关于状态的方法(比如resolve( ),reject( )),因此新对象的执行状态就不能被改变。
let dtd = $.Deferred() //创建一个deferred对象 const wait = (dtd) => { const tasks = () => { alert('Over!') dtd.resolve() // 改变deferred对象的执行状态 } setTimeout(tasks, 5000) return dtd.promise() // 返回promise对象 } let d = wait(dtd) // 新建一个对象,对这个对象进行操作 $.when(d) .done(function() { alert('Success') }) .fail(function() { alert('Fail!') }) d.resolve()
上面代码最后的d.resolve( )已经是一个无效的了,因为在前面我们已经用dtd.promise( )方法把dtd这个deferred对象转为了一个promise对象。前面提到了,promise对象并没有关于状态的方法。但是promise对象有done( ),fail() 这样的方法。所以这段代码依然能够跑通。
当然了,在函数内部创建deferred对象也是ok的:
const wait = (dtd) => {
//创建一个deferred对象 let dtd = $.Deferred() const tasks = () => { alert('Over!') // 改变deferred对象的执行状态
dtd.resolve() } setTimeout(tasks, 5000)
// 返回promise对象 return dtd.promise() } $.when(wait()) .done(function() { alert('Success') }) .fail(function() { alert('Fail!') })
$.Deferred( )接受参数
另一种防止外部改变执行状态的方法是使用deferred对象的构造函数$.Deferred( )
const wait = (dtd) => { const tasks = () => { alert('Over!')
// 改变deferred对象的执行状态 dtd.resolve() } setTimeout(tasks, 5000)
// 返回promise对象 return dtd.promise() } $.Deferred(wait) .done(function() { alert('Success') }) .fail(function() { alert('Fail!') })
这里需要注意的是Deferred( )里是以函数的名字作为参数。$.Deferred( )生成的deferred对象就自动被当作wait( )函数的参数。
直接在wait( )对象上部署
let dtd = $.Deferred() const wait = (dtd) => { const tasks = () => { alert('Over!') dtd.resolve() // 改变deferred对象的执行状态 } setTimeout(tasks, 5000) return dtd.promise() // 返回promise对象 } dtd.promise(wait) wait.done(function() { alert('Success') }) .fail(function() { alert('Fail!') }) wait(dtd)
跟前面同理,用promise( )方法把dtd这个deferred对象转成promise对象,同时生成的promise对象用作wait( )函数的参数。
总结
$.Deferred( ) => 创建一个deferred对象。
deferred.done( ) => 指定操作成功时的回调函数。
deferred.fail( ) => 指定操作失败时的回调函数。
deferred.promis( ) => 返回一个promise对象,promise对象执行状态无法改变。
deferred.resolve( ) => 手动改变deferred对象的执行状态为“已完成”。
deferred.reject( ) => 手动改变deferred对象的执行状态为“已失败”。
$.when( ) => 为多个操作指定回调函数。
deferred.then( ) => 把done( )方法和fail( )方法写到一起
$.when('example.php') .then(successFunc, failureFunc)
then()接受两个参数,第一个代表done(),第二个代表fail()。如果then()只有一个参数的时候,那么这个参数默认就是done().
deferred.always( ) => 不管deferred对象的状态如何,都指定这个回调函数
$.ajax('example.php').always(function() { alert('永远执行!') })
time
以上是关于jQuery的deferred对象和promise对象的主要内容,如果未能解决你的问题,请参考以下文章