jQuery的deferred对象解析
deferred对象定义了多种方法,具体说明如下:
-
$.Deferred():生成一个deferred对象。
-
deferred.done():指定操作成功时的回调函数。
-
deferred.fail():指定操作失败时的回调函数。
-
deferred.promise():没有参数时,返回一个新的deferred对象,该对象的运行状态无法被改变;接收参数时,其用为在参数对象上部署deferred接口。
-
deferred.resolve():手动改变deferred对象的运行状态为“已完成”,从而立即触发done()方法
-
deferred.reject():与deferred.resolve()正好相反,调用后将deferred对象的运行状态变为“已失败”,从而立即触发fail()方法。
-
$.when():为多个操作指定回调函数
$.when($.ajax("/main.php")).then(successFunc,failureFunc);
-
deferred.then():有时为了省事,可以把done()和fail()合在一起写,这就是then()方法。
如果then()有两个参数,那么第一个参数是done()方法的回调函数,第二个参数是fail()方法的回调函数。如果then()只有一个参数,那么等同于done()。 -
deferred.always():定义回调函数,它的作用是不管调用的是deferred.resolve()还是deferred.reject(),最后总是执行
1.指定同一操作的多个回调函数
$.ajax("test.html")
.done(function () { console.log("哈哈,成功了!"); })
.fail(function () { console.log("出错啦!"); }) //如果fail,就不会执行后面的:第二个回调函数!
.done(function(){ console.log("第二个回调函数!");} );
2.为多个操作指定回调函数
deferred对象的另一大好处,就是它允许你为多个事件指定一个回调函数,这是传统写法做不到的。
请看下面的代码,它用到了一个新的方法$.when():
<script>
$.when($.ajax("test1.html"), $.ajax("test2.html"))
.done(function (xhr) {
console.log(xhr); //这里的xhr没有返回test1
console.log("哈哈,成功了!"); } //都成功了,就运行done()指定的回调函数
)
.fail(function () { console.log("出错啦!"); }); //如果有一个失败或都失败了,就执行fail()指定的回调函数。
</script>
这段代码的意思是,先执行两个操作$.ajax("test.html")和$.ajax("test1.html"),如果都成功了,就运行done()指定的回调函数;如果有一个失败或都失败了,就执行fail()指定的回调函数。
3.普通操作的回调函数接口(上)
jQuery.when()
jQuery.when( deferreds )
deferreds
类型: Deferred 或 Promise 或 Thenable
零个或多个延迟对象,或者普通的JavaScript对象。
在多个延迟对象传递给jQuery.when()
的情况下,该方法根据一个新的“宿主” Deferred(延迟)对象,跟踪所有已通过Deferreds聚集状态,返回一个Promise对象。
1.当所有的延迟对象被解决(resolve)时,“宿主” Deferred(延迟)对象才会解决(resolved)该方法,
2.或者当其中有一个延迟对象被拒绝(rejected)时,“宿主” Deferred(延迟)对象就会reject(拒绝)该方法。
3.如果“宿主” Deferred(延迟)对象是(resolved)解决状态时, “宿主” Deferred(延迟)对象的 doneCallbacks (解决回调)将被执行。
4.参数传递给 doneCallbacks提供这解决(resolved)值给每个对应的Deferreds对象,并匹配Deferreds传递给 jQuery.when()
的顺序
var d1 = $.Deferred();
var d2 = $.Deferred();
var d3 = $.Deferred();
$.when(d1, d2, d3).done(function (v1, v2, v3) {
console.log(v1); // v1 is undefined 4.匹配Deferreds传递给 `jQuery.when()`的顺序
console.log(v2); // v2 is "abc"
console.log(v3); // v3 is an array [ 1, 2, 3, 4, 5 ]
}).fail(function (s1, s2, s3) {
console.log(s1); // s1 is 1 //2.2.或者当其中有一个延迟对象被拒绝(rejected)时
console.log(s2); // s2 is 2
console.log(s3); // s3 is 3
});
//d1.reject(); //2.1 或者当其中有一个延迟对象被拒绝(rejected)时
d1.resolve(); //3.如果“宿主” Deferred(延迟)对象是(resolved)解决状态时
d2.resolve("abc");
d3.resolve(1, 2, 3, 4, 5); //1.当所有的延迟对象被解决(resolve)时
$.When的应用
任何一个操作----不管是ajax操作还是本地操作,也不管是异步操作还是同步操作----都可以使用deferred对象的各种方法,指定回调函数。
我们来看一个具体的例子。假定有一个很耗时的操作wait:
var wait = function () {
var tasks = function () {
console.log("1 现在执行完毕!");
};
setTimeout(tasks, 5000);
};
$.when(wait())
.done(function () { console.log("2 哈哈,最后我们成功了!"); }) //立即执行
.fail(function () { console.log("3 出错啦!"); });
//输出:
2 哈哈,最后我们成功了!
(过5秒)
1 现在执行完毕!
$.when()的参数只能是deferred对象
但是,这样写的话,done()方法会立即执行,起不到回调函数的作用。原因在于 $.when()的参数只能是deferred对象 ,所以必须对wait()进行改写:
var dtd = $.Deferred(); // 新建一个deferred对象
var wait = function (dtd) {
var tasks = function () {
console.log("1 现在执行完毕!");
dtd.resolve(); // 改变deferred对象的执行状态
};
setTimeout(tasks, 5000);
return dtd; //返回这里deferred对象
};
$.when(wait(dtd))
.done(function () { console.log("2 哈哈,最后我们成功了!"); })
.fail(function () { console.log("3 出错啦!"); });
//输出:
(过5秒)
1 现在执行完毕!
2 哈哈,最后我们成功了!
wait()函数运行完,就会自动运行done()方法指定的回调函数。
4.deferred.resolve()方法和deferred.reject()方法
deferred对象有三种执行状态----未完成,已完成和已失败。如果执行状态是"已完成"(resolved),deferred对象立刻调用done()方法指定的回调函数;如果执行状态是"已失败",调用fail()方法指定的回调函数;如果执行状态是"未完成",则继续等待,或者调用progress()方法指定的回调函数(jQuery1.7版本添加)。
5.deferred.promise()方法
其实就是创建一个当前 Defferred 的只读副本,防止被外部改变。
jQuery提供的
deferred.promise()
方法的作用是,在原来的Deferred 对象上返回另一个 Deferred 对象,即受限制的 Promise 对象,受限制的 Promise 对象只开放与改变执行状态无关的方法(比如done()方法和fail()方法),屏蔽与改变执行状态有关的方法(比如resolve()方法和reject()方法),从而使得执行状态不能被改变。
前面的例子dtd是一个全局对象,所以它的执行状态可以从外部改变。
var wait = function (dtd) {
var tasks = function () {
console.log("执行完毕!");
dtd.resolve();
};
setTimeout(tasks, 5000);
return dtd;
};
$.when(wait(dtd))
.done(function () { console.log("哈哈,成功了!"); })
.fail(function () { console.log("出错啦!"); });
dtd.resolve(); // 这里改变状态后,直接就执行done了
//输出:
哈哈,成功了!
(过5秒)
执行完毕!
为了避免这种情况,jQuery提供了deferred.promise()方法。屏蔽与改变执行状态有关的方法(比如resolve()方法和reject()方法)
var dtd = $.Deferred();
var wait = function (dtd) {
var tasks = function () {
console.log("执行完毕!");
dtd.resolve();
};
setTimeout(tasks, 5000);
return dtd.promise(); //返回这里promise对象
};
var d = wait(dtd);
$.when(d)
.done(function () { console.log("哈哈,成功了!"); })
.fail(function () { console.log("出错啦!"); });
d.resolve(); // //d 是一个 promise对象,没有resolve方法. 此时,这个语句是无效的
更好的方法是
var wait = function (dtd) {
var dtd = $.Deferred(); //在函数内部,新建一个Deferred对象
var tasks = function () {
console.log("执行完毕!");
dtd.resolve();
};
setTimeout(tasks, 5000);
return dtd.promise(); //返回这里promise对象
};
$.when(wait())
.done(function () { console.log("哈哈,成功了!"); })
.fail(function () { console.log("出错啦!"); });
6.另外一种写法,deferred对象的建构函数$.Deferred()(一般用第1种)
另一种防止执行状态被外部改变的方法,是使用deferred对象的建构函数$.Deferred()。
这时,wait函数还是保持不变,我们直接把它传入$.Deferred():
$.Deferred(wait)
.done(function(){ alert("哈哈,成功了!"); })
.fail(function(){ alert("出错啦!"); });
jQuery规定,$.Deferred()可以接受一个函数名(注意,是函数名)作为参数,$.Deferred()所生成的deferred对象将作为这个函数的默认参数。
7.普通操作的回调函数接口(下):第3种写法(一般用第1种)
除了上面两种方法以外,我们还可以直接在wait对象上部署deferred接口。
var dtd = $.Deferred();
var wait = function(dtd){
var tasks = function(){
alert("执行完毕!");
dtd.resolve();
};
setTimeout(tasks,5000);
};
dtd.promise(wait); //重点,wait对象上部署Deferred接口,直接调用done和fail
wait.done(function(){ alert("哈哈,成功了!"); })
.fail(function(){ alert("出错啦!"); });
wait(dtd);
这里的关键是 dtd.promise(wait) 这一行,它的作用就是在wait对象上部署Deferred接口 。正是因为有了这一行,后面才能直接在wait上面调用done()和fail()。
8.deferred.then() 快捷写法
有时为了省事,可以把done()和fail()合在一起写,这就是then()方法。
$.when($.ajax( "/main.php" ))
.then(successFunc, failureFunc );
如果then()有两个参数,那么第一个参数是done()方法的回调函数,第二个参数是fail()方法的回调方法。如果then()只有一个参数,那么等同于done()。
更新一下
then方法的作用也是指定回调函数,它可以接受三个参数,也就是三个回调函数。第一个参数是resolve时调用的回调函数(相当于done方法),第二个参数是reject时调用的回调函数(相当于fail方法),第三个参数是progress()方法调用的回调函数。
返回值
在jQuery 1.8之前,then()只是.done().fail()写法的语法糖,两种写法是等价的。在jQuery 1.8之后,then()返回一个新的promise对象,而done()返回的是原有的deferred对象。如果then()指定的回调函数有返回值,该返回值会作为参数,传入后面的回调函数。
9. Durandal 实例
self.loadContryList = function (num) {
//system.defer 可以替换成 jquery 的 $.Deferred
return system.defer(function (dfd) {
membershipVAT.getAllCountryList().done(function (xhr) {
setTimeout(function () { console.log(num); }, 3000);
return dfd.resolve(true);
}).fail(function (xhr) {
return dfd.resolve(false);
})
}).promise();
}
var der = self.loadContryList(1);
der.then(function (xhr) {
console.log('aaaaaaaaaaaaaaaa');
return self.loadContryList(2);
}).then(function (data) {
return self.loadContryList(3);
}).then(function (data) {
return self.loadContryList(4);
});
//----------------------------单个控制
self.getProjectTaskLinguisticQA = function () {
//var tasks = function () {
return system.defer(function (dtd) {
console.log("执行完毕1!");
dtd.reject(1);
//};
//setTimeout(tasks, 1000);
}).promise();
}
self.getProjectTaskLinguisticQA2 = function () {
//var tasks = function () {
return system.defer(function (dtd) {
dtd.reject(2);
console.log("执行完毕2!");
//};
//setTimeout(tasks, 4000);
}).promise();
}
self.getProjectTaskLinguisticQA3 = function () {
//var tasks = function () {
return system.defer(function (dtd) {
dtd.resolve(3);
//dtd.reject(4);
console.log("执行完毕3!");
//};
//setTimeout(tasks, 4000);
}).promise();
}
self.getProjectTaskLinguisticQA3()
.done(function (a, b) {
console.log("成功了g3")
self.getProjectTaskLinguisticQA()
.done(function (a, b) {
console.log("成功了g1")
self.getProjectTaskLinguisticQA2()
.done(function (i, j) {
console.log("成功了g2")
})
.fail(function (c, d) {
console.log("失败了g2")
});
})
.fail(function (c, d) {
console.log("失败了g1")
})
})
.fail(function (c, d) {
console.log("失败了g3")
})
链式调用
then
function asyncRequest(number) {
// 假设这是一个返回 Promise 的异步操作,比如 AJAX 请求
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() < 0.5) {
// 模拟成功的情况
console.log('异步请求 ' + number + ' 成功');
resolve(number);
} else {
// 模拟失败的情况
console.log('异步请求 ' + number + ' 失败');
reject(number);
}
}, number * 1000); // 假设每个请求需要不同的时间来完成
});
}
// 开始执行第一个异步请求,并链式调用后续请求
asyncRequest(1)
.then((result1) => {
console.log('第一个请求执行完成');
return asyncRequest(2); // 返回第二个请求的 Promise
})
.then((result2) => {
console.log('第二个请求执行完成');
// 同时执行第三和第四个请求,使用 Promise.all 等待它们完成
return Promise.all([
asyncRequest(3),
asyncRequest(4)
]);
})
.then(([result3, result4]) => {
console.log('第三和第四个请求执行完成');
return asyncRequest(5); // 返回第五个请求的 Promise
})
.then((result5) => {
console.log('所有请求执行完毕,最终结果是:', result5);
})
.catch((error) => {
// 捕获任何地方的错误
console.log('某个请求失败:', error);
});
done和fail
而且不用then
function asyncRequest(number) {
var defer = $.Deferred();
// 假设这是一个异步操作,比如 AJAX 请求
setTimeout(function() {
if (Math.random() < 0.5) {
// 模拟成功的情况
console.log('异步请求 ' + number + ' 成功');
defer.resolve(number);
} else {
// 模拟失败的情况
console.log('异步请求 ' + number + ' 失败');
defer.reject(number);
}
}, number * 1000); // 假设每个请求需要不同的时间来完成
return defer.promise();
}
// 开始执行第一个异步请求,并链式调用后续请求
asyncRequest(1).done(function(result1) {
console.log('第一个请求执行完成');
return asyncRequest(2); // 返回第二个请求的 promise
}).done(function(result2) {
console.log('第二个请求执行完成');
// 同时执行第三和第四个请求,使用 $.when 等待它们完成
return $.when(
asyncRequest(3),
asyncRequest(4)
);
}).done(function(result3, result4) {
console.log('第三和第四个请求执行完成');
// 由于 $.when 返回的是一个包含所有结果的数组,
// 所以我们需要使用 result3 和 result4 来分别获取它们
return asyncRequest(5); // 返回第五个请求的 promise
}).done(function(result5) {
console.log('所有请求执行完毕,最终结果是:', result5);
}).fail(function(error) {
console.log('某个请求失败:', error);
});
在这个例子中,每个 asyncRequest 函数返回一个 promise 对象,它可以在成功时通过 .done() 方法链式调用下一个请求,或者在失败时通过 .fail() 方法处理错误。当需要同时执行多个请求时(如第三和第四个请求),我们使用 $.when() 来等待它们完成。
请注意,.when() 方法返回一个新的 promise 对象,这个对象在所有给定的 promises 都完成时解析,并且其解析值是一个包含所有原 promises 解析值的数组。因此,在 .done() 的回调函数中,我们使用 result3 和 result4 来分别获取第三和第四个请求的结果。
这样,您就可以通过链式调用 .done() 方法来顺序执行异步请求,并在每个请求成功时启动下一个请求。如果在任何时候发生错误,.fail() 方法将被调用,并且错误会被传递给相应的回调函数。
参考:
jQuery的deferred对象详解:http://www.ruanyifeng.com/blog/2011/08/a_detailed_explanation_of_jquery_deferred_object.html