CommonJS
Promise/A规范这一重量级方案,不过没有严格按照规范进行实现,有一些API上的差异。
(1) $.Deferred() 生成一个deferred对象。
(2) deferred.done() 指定操作成功时的回调函数
(3) deferred.fail() 指定操作失败时的回调函数
(4) deferred.promise() 没有参数时,返回一个新的deferred对象,该对象的运行状态无法被改变;接受参数时,作用为在参数对象上部署deferred接口。(也就是说promise对象不允许你调用resolve和reject方法,promise是deferred的只读版,或者更通俗地理解成promise是一个对将要完成的任务的承诺。)
(5) deferred.resolve() 手动改变deferred对象的运行状态为"已完成",从而立即触发done()方法。
(6)deferred.reject() 这个方法与deferred.resolve()正好相反,调用后将deferred对象的运行状态变为"已失败",从而立即触发fail()方法。
(7) $.when() 为多个操作指定回调函数。
除了这些方法以外,deferred对象还有二个重要方法,上面的教程中没有涉及到。
{I}when方法里面要执行多少个操作,回调函数就有多少个参数,对应前面每一个操作的返回结果。
$.when({testing:123}).done(function(x){
console.log(x.testing); //"123"
});
/**上面代码指定的回调函数,将在when方法后面立即执行。利用这个特点,我们可以写一个具有缓存效果的异步操作函数。也就是说,第一次调用这个函数的时候,将执行异步操作,后面再调用这个函数,将会返回缓存的结果。如下:*/
function maybeAsync(num){
var dfd = $.Deferred();
if(num === 1){
setTimeout(function(){
dfd.resolve(num);
},100);
return dfd.promise();
}
return num;
}
$.when(maybeAsync(1)).then(function(resp){
$('#target').append(' ' + resp +'
});
$.when(maybeAsync(0)).then(function(resp){
$('#target').append(' '+resp+'
});
/*上面代码如果maybeAsync函数的参数为1,则执行异步操作,否则立即返回缓存的结果*/
(8)deferred.then()
有时为了省事,可以把done()和fail()合在一起写,这就是then()方法。
$.when($.ajax( "/main.php" ))
.then(successFunc, failureFunc );
如果then()有两个参数,那么第一个参数是done()方法的回调函数,第二个参数是fail()方法的回调方法。如果then()只有一个参数,那么等同于done()。
(9)deferred.always()
这个方法也是用来指定回调函数的,它的作用是,不管调用的是deferred.resolve()还是deferred.reject(),最后总是执行。
$.ajax( "test.html" )
.always( function() { alert("已执行!");} );
(10)state方法
该方法用来返回deferred对象目前的状态。
var deferred = new $.Deferred();
deferred.state(); //"pending"
deferred.resolve();
deferred.state(); //"resolved"
该方法的返回值有三个:
pendding:表示操作还没有完成
resolved:表示操作成功
rejected:表示操作失败
(11)notify()和progress()
progress()用来指定一个回掉函数,当调用notify()方法的时候,该回掉函数将执行。它的用意是提供一个接口,使得在非同步操作执行过程中,可以执行某些操作,比如:定期返回进度条的进度。
var userProgress = $.Deferred();
var $profileField = $('input');
var totalFields = $profileField.length;
userProgress.progress(function(fieldFields){
var pctComplete = (fieldFields/totalFields)*100;
$('#progress').html(pctComplete.toFixed(0));
});
userProgress.done(function(){
$('#thanks').html("谢谢完成profile").show();
});
$('#input').on('change',function(){
var fieldFields = $profileFields.filter('[value!=''']').length;
userProgress.notify(fieldFields);
if(fieldFields == totalFields){
userProgress.resolve();
}
});
(12)pipe方法
pipe方法接受一个函数作为参数,表示在调用then()方法,done()方法,fail()方法,always()方法指定的回调函数之前,先运行pipe()方法指定的回调函数。它通常用来对服务器返回的数据做初步处理。
(13)wait()方法
可以用deferred对象写一个wait()方法,表示等待多少毫秒后再执行。
$wait = function(time){
return $.Deferred(function(df){
setTimeout(dfd.resolve,time);
});
}
//使用方法如下:
$.wait(5000).then(function(){
alert("Helo")
})
/*改写setTimeout方法*/
在上面的wait方法的基础上,还可以改写setTimeout方法,让其返回一个deferred对象。
代码如下:
function doSomethingLater(fn, time) {
var dfd = $.Deferred();
setTimeout(function() {
dfd.resolve(fn());
}, time || 0);
return dfd.promise();
}
var promise = doSomethingLater(function (){
console.log( '已经延迟执行' );
}, 100);
自定义操作使用deferred接口
我们可以利用deferred接口,使得任意操作都可以用done()和fail()指定回调函数。
代码如下:
Twitter = {
search:function(query) {
var dfr = $.Deferred();
$.ajax({
url:"http://search.twitter.com/search.json",
data:{q:query},
dataType:'jsonp',
success:dfr.resolve
});
return dfr.promise();
}
}
使用方法如下:
代码如下:
Twitter.search('intridea').then(function(data) {
alert(data.results[0].text);
});
deferred对象的另一个优势是可以附加多个回调函数。代码如下:
function doSomething(arg) {
var dfr = $.Deferred();
setTimeout(function() {
dfr.reject("Sorry, something went wrong.");
});
return dfr;
}
doSomething("uh oh").done(function() {
alert("Won't happen, we're erroring here!");
}).fail(function(message) {
alert(message)
});
2、Promise对象
Promise 对象用来进行延迟(deferred) 和异步(asynchronous ) 计算。回顾一下在JQuery 1.5之前,传统的Ajax操作写法:
$.ajax()操作完成后,如果使用的是低于1.5.0版本的jQuery,返回的是XHR对象,你没法进行链式操作;如果高于1.5.0版本,返回的是deferred对象,可以进行链式操作。
现在,新的写法是这样的:
在jQuery 1.6之前,
always()
相当于complete()
,在done()
或fail()
执行完毕之后才执行,即无论Ajax的执行结果是什么,always()
总会执行。
done()
,fail()
, 和always()
会返回同一个JQuery XMLHttpRequest(jqXHR)对象,所以可以进行链式调用:deferred对象的一大好处,就是它允许你自由添加多个回调函数。
回调函数可以添加任意多个,它们按照添加顺序执行。如果在后续的代码中还需要利用改jqXHR对象,就必须用变量保存:
另外一种产生链式调用的方式是利用Promise的 then 方法,它接受三个event handlers作为参数,在jquery 1.8之前,对于多个回调函数,有需要以数组方式传入三个参数:
1.8版本之后,
then
会返回一个新的Promise,它可以通过一个函数过滤掉Deferred对象的状态和值,用于取代不被推荐使用的 deferred.pipe() 方法。
then()
方法还能逐次调用多个方法,可以用于处理有着先后顺序或者依赖的多个Ajax请求:
Promise的状态
在任何时刻,Promise只能处于三种状态之一:未完成(unfulfilled)、已完成(resolved)和已失败(resolved)。promise默认的状态是unresolved,任何处于回调队列的函数都会被执行。举个粟子,如果一个Ajax调用成功,
$.resolved
会被调用,同时promise的状态转为resolved,以及任何监听done
的回调都会被执行;相反,则$.rejected
会被调用,同时promise的状态转为rejected,以及任何监听fail
的回调都会被执行。对上述的wait进行改写:
现在,wait()函数返回的是deferred对象,这就可以加上链式操作了。
在ajax操作中,deferred对象会根据返回结果,自动改变自身的执行状态;但是,在wait()函数中,这个执行状态必须由程序员手动指定。调用dtd.resolve(),将dtd对象的执行状态从”未完成”改为”已完成”,从而触发done()方法。调用dtd.reject(),将dtd对象的执行状态从”未完成”改为”已失败”,从而触发fail()方法。
但是这种写法是有问题的,因为dtd是一个全局对象,所以它的执行状态可以从外部改变。
我在代码的尾部加了一行dtd.resolve(),这就改变了dtd对象的执行状态,因此导致done()方法立刻执行,跳出”哈哈,成功了!”的提示框,等5秒之后再跳出”执行完毕!”的提示框。
为了避免这种情况,可以在内部建立Deferred对象:
另外一个方式是利用 deferred.promise() 方法:
The deferred.promise() method allows an asynchronous function to prevent other code from interfering with the progress or status of its internal request. The Promise exposes only the Deferred methods needed to attach additional handlers or determine the state (then, done, fail, always, pipe, progress, state and promise), but not ones that change the state (resolve, reject, notify, resolveWith, rejectWith, and notifyWith).
也就是说,
deferred.promise()
只是阻止其他代码来改变这个 deferred 对象的状态。可以理解成,通过deferred.promise()
方法返回的 deferred promise 对象,是没有 resolve ,reject, progress , resolveWith, rejectWith , progressWith 这些可以改变状态的方法,你只能使用 done, then ,fail 等方法添加 handler 或者判断状态。
/*pipe的深入理解:执行pipe时候会判断pipe执行的时候接受的参数,如果是一个值,则直接接受这个值,pipe就立即执行;如果是一个deferred/promise,pipe()则会等待这个deferred/promise执行后才会执行*/
$.Deferred().resolve(1).promise()
.pipe(function(a){
/*a为promise对象*/
console.log(a === 1) //输出true
return $.Deferred().resolve(2).promise();
}).pipe(function(b){
/*b为promise对象*/
console.log(b === 2) //输出true
})
design/q4.js中,为了实现这一点,新增了一个工具函数ref:
这是在着手处理与promise关联的value。这个工具函数将对任一个value值做一次包装,如果是一个promise,则什么也不做,如果不是promise,则将它包装成一个promise。注意这里有一个递归,它确保包装成的promise可以使用then方法级联。为了帮助理解它,下面是一个使用的例子: