1.jQuery.Defered
1.1 什么是 deferred 对象
开发网站过程中,我们经常遇到某些耗时很长的 JavaScript 操作。
其中,既有异步的操作(比如 ajax 读取服务器数据),也有同步的操作(比如遍历一个大型数组),它们都不是立即能得到结果的。
通常的做法是,为它们指定回调函数,即事先规定,一旦它们运行结束,应该调用哪些函数。
简单说,defered 对象就是 jQuery 的回调函数解决方法,它是 jQuery 1.5.0 版本开始引入的新功能。
1.2 ajax 的链式写法
jQuery 1.5以下版本的 ajax 操作的传统写法如下:
$.ajax({
url:"test.html",
success:function(){
alert("成功了!");
},
error:function(){
alert("失败了!");
}
});
上面的代码中,$.ajax() 接受一个参数对象,这个对象包含两个方法:success 方法指定操作成功后的回调函数,error 方法指定操作失败后的回调函数。
jQuery 1.5以上版本,新的写法如下:
$.ajax("test.html")
.done(function(){ alert("成功了!"); })
.fail(function(){ alert("失败了!");});
可以看到,done() 方法相当于 success 方法,fail() 相当于 error 方法。
1.3 指定同一操作的多个回调函数
deferred 对象的一大好处,就是它允许自由添加多个回调函数。直接把新的回调函数添加到后面就行了:
$.ajax("test.html")
.done(function(){ alert("成功了!"); })
.fail(function(){ alert("失败了!");})
.done(function(){ alert("这里是第二个回调函数!"); });
回调函数可以任意多个,它们按照添加顺序执行。
1.4 为多个操作指定回调函数
deferred 对象的另一好处,就是它允许你为多个事件指定一个回调函数,这是传统写法做不到的。
它用到了一个新的方法 $.when():
$.when($.ajax("test1.html"),$.ajax("test.html"))
.done(function(){ alert("成功了!"); })
.fail(function(){ alert("失败了!"); });
这段代码的意思是,先执行两个操作 $.ajax("test1.html") 和 $.ajax("test.html"),如果都成功了,就运行 done() 指定的回调函数;如果有一个失败了,就执行 fail() 回调函数。
1.5 普通操作的回调函数接口(上)
deferred 对象的最大优点,就是它把这一套回调函数接口,从 ajax 操作扩展到了所有操作。也就是说,任何一个操作 —— 不管它是 ajax 还是本地操作,也不管是异步操作还是同步操作 —— 都可以使用 deferred 对象的各个方法,指定回调函数。
我们来看一个具体的例子。假定有一个很耗时的操作 wait:
var wait = function(){
var tasks = function(){
alert("执行完毕!");
};
setTimeout(tasks,5000);
}
很自然的,你会想到,可以使用 $.when():
$.when(wait())
.done(function(){ alert("成功了!"); })
.fail(function(){ alert("失败了!"); });
但是,这样写的话,done() 方法会立即执行,起不到回调函数的作用。原因在于 $.when() 的参数只能是 deferred 对象,所以必须对 wait() 进行改写:
var dtd = $.Deferred(); // 新建一个 deferred 对象
var wait = function(){
var tasks = function(){
alert("执行完毕!");
dtd.resolve(); // 改变 deferred 对象的执行状态
}
setTimeout(tasks,5000);
return dtd;
}
现在,wait() 返回的是 deferred 对象,这就可以加上链式操作了。wait() 函数运行完,就会自动运行 done() 方式指定的回调函数。
1.6 deferred.resolve() 方法和 deferred.reject() 方法
jQuery 规定,deferred 对象有三种执行状态 —— 未完成,已完成和已失败。如果执行状态是“已完成”(resolved),deferred 对象立即调用 done() 方法指定的回调函数;如果执行状态是“已失败”,调用 fail() 方法指定的回调函数;如果执行状态是“未完成”,则继续等待,或者调用 progress() 方法指定的回调函数(jQuery 1.7版本添加)。
前面部分的 ajax 操作时,deferred 对象会根据返回结果,自动改变自身的执行结果但是,在 wait() 函数中,这个执行状态必须由程序员手动指定。 dtd.resolve() 的意思是,将 dtd 对象的执行状态从“未完成”改为“已完成”,从而触发 done() 方法。
类似的,还是存在一个 deferred.reject() 方法,作用是将 dtd 对象的执行状态从“未完成”改为“已失败”,从而触发 fail() 方法。
1.7 deferred.promise() 方法
上面这种写法,还是有问题。那就是 dtd 是一个全局对象,所以它的执行状态可以从外部改变。
var dtd = $.Deferred(); // 新建一个 deferred 对象
var wait = function(){
var tasks = function(){
alert("执行完毕!");
dtd.resolve(); // 改变 deferred 对象的执行状态
}
setTimeout(tasks,5000);
return dtd;
}
$.when(wait())
.done(function(){ alert("成功了!"); })
.fail(function(){ alert("失败了!"); });
dtd.resolve();
上面的代码尾部加了一行 dtd.resolve() ,这就改变了 dtd对象的执行状态,因此导致 done() 方法立即执行,跳出“成功了!”的提示框,等5秒之后再跳出“执行完毕!”的提示框。
为了避免这种情况,jQuery 提供了 deferred.promise() 方法。它的作用是,在原来的 deferred 对象上返回另一个 deferred 对象,后者只开放与改变执行状态无关的方法(比如 done() 方法和 fail() 方法),屏蔽与改变执行状态有关的方法(比如 resolve() 方法和 reject() 方法),从而使得执行状态不能改变。
请看下面代码:
var dtd = $.Deferred(); // 新建一个 deferred 对象
var wait = function(){
var tasks = function(){
alert("执行完毕!");
dtd.resolve(); // 改变 deferred 对象的执行状态
}
setTimeout(tasks,2000);
return dtd.promise(); // 返回 promise 对象
}
var d = wait(); // 新建一个d对象,改为对这个对象进行操作
$.when(d)
.done(function(){ alert("成功了!"); })
.fail(function(){ alert("失败了!"); });
d.resolve(); //此时,这个语句是无效的
在上面的这段代码中,wait() 函数返回的是 promise 对象。然后,我们把回调函数绑定在这个对象上面,而不是原来的 deferred 对象上面。这样的好处是,无法改变这个对象的执行状态,要想改变执行状态,只能操作原来的 deferred 对象。
不过,更好的写法是将 dtd 对象变成 wait() 函数的内部对象。
var wait = function(){
var dtd = $.Deferred(); // 新建一个 deferred 对象
var tasks = function(){
alert("执行完毕!");
dtd.resolve(); // 改变 deferred 对象的执行状态
}
setTimeout(tasks,2000);
return dtd.promise(); // 返回 promise 对象
}
$.when(wait())
.done(function(){ alert("成功了!"); })
.fail(function(){ alert("失败了!"); });
1.8 普通操作的回调函数接口(中)
另一种防止执行状态被外部改变的方法是,使用 deferred 对象的构建函数 $.Deferred()。
这时,wait 函数还是保持不变,我们直接将它传入 $.Deferred:
$.Deferred(wait)
.done(function(){ alert("成功了!"); })
.fail( function(){ alert("失败了!"); } );
jQuery 规定,$.Deferred() 可以接受一个函数名(注意,是函数名)作为参数,$.Deferred() 所生成的 deferred 对象将作为这个函数的默认参数。
1.9 普通操作的回调函数接口(下)
除了上面两种方法以外,我们还可以直接在 wait 对象上部署 deferred 接口。
var dtd = $.Deferred(); // 新建一个 deferred 对象
var wait = function(){
var tasks = function(){
alert("执行完毕!");
dtd.resolve(); // 改变 deferred 对象的执行状态
}
setTimeout(tasks,2000);
}
dtd.promise(wait);
wait.done(function(){ alert("成功了!"); })
.fail(function(){ alert("失败了!"); });
wait(dtd);
这里的关键是 dtd.promise(wait); 这一行,它的作用就是在 wait 对象上部署 Deferred 接口。正是因为有了这一行,后面才能直接在 wait 上面调用 done() 和 fail() 方法
1.10 小结:defered 对象的方法
前面已经讲到了 deferred 对象的多种方法,下面做一个总结:
(1) $.Deferred() 生成一个 deferred 对象。
(2) deferred.done() 指定操作成功时的回调函数。
(3) deferred.fail() 指定操作失败时的回调函数。
(4) deferred.promise() 没有参数时,返回一个新的 deferred 对象该对象的运行状态无法被改变;接受参数时,作用为参数对象上部署 deferred 接口。
(5) deferred.resolve() 手动改变 deferred 对象的运行状态为“已完成”,从而触发 done() 方法。
(6) deferred.reject() 这个方法与 defered.resolve() 正好相反,调用后将 deferred 对象的运行状态变为“已失败”,从而触发 fail() 方法。
(7) $.when() 为多个操作指定回调函数。
除了这些方法以外,deferred 对象还有两个重要方法:
(8) deferred.then() :
有时为了省事,可以把 done() 和 fail() 合在一起写,这就是 then() 方法:
$.when($.ajax("/main.php"))
.then(successFunc,failFunc)
如果 then() 有两个参数,那么第一个是 done() 方法的回调函数,第二个参数是 fail() 方法的回调函数。如果 then() 只有一个函数,那么等同于 done()。
(9) deferred.always():
这个方法也是用来指定回调函数的,它的作用是,不管调用的是 deferred.resolve() 还是 deferred.reject(),最后总是执行。
$.ajax("test.html")
.always(function(){ alert("已执行!"); });
<br />
2.Q.js
Q.js 是知名、功能完整的 Promise 函式库,已经实现了 Promise/A 标准。
特点:
- 浏览器端和服务器端共用
- 发展早,较其他库相对成熟
- Github:https://github.com/kriskowal/q
- 官方文档:http://documentup.com/kriskowal/q/
标准的回调函数的方式如下,嵌套比较深:
step1(function (value1) {
step2(value1, function(value2) {
step3(value2, function(value3) {
step4(value3, function(value4) {
// Do something with value4
});
});
});
});
使用Q.js 后的代码如下,采用依次排列:
Q.fcall(promisedStep1)
.then(promisedStep2)
.then(promisedStep3)
.then(promisedStep4)
.then(function (value4) {
// Do something with value4
})
.catch(function (error) {
// Handle any error from all above steps
})
.done();
来个简单示例如下:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Q.js</title>
<script src="../js/q-1.4.1.min.js"></script>
</head>
<body>
<script>
function qtest(num) {
return Q.delay(num, 1000);
}
Q.all([
qtest(10),
qtest(20),
qtest(30)
]).spread(function (x, y, z) {
return x + y + z;
}).done(function (str) {
console.log("The sum is " + str + ".")
});
</script>
</body>
</html>
输出结果为:
The sum is 60.
API 文档地址:https://github.com/kriskowal/q/wiki/API-Reference
<br />
3.Koajs
Koajs 是基于 Node.js 平台的下一代 Web 开发框架。使用 koa 编写 web 应用,通过组合不同的 generator,可以免除重复繁琐的回调函数嵌套,并极大地提升常用错误处理效率。Koa 不在内核方法中绑定任何中间件,它仅仅提供了一个轻量优雅的函数库,使得编写 Web 应用变得得心应手。
特点:
- 基于 Generator,消灭回调代码
- 强大的异常处理
- 实现了基础的 http 工程,其他通过 middleware 实现、灵活
- 官网地址:http://koajs.com/
- 中文版地址:http://koa.bootcss.com/
示例:
<script>
function * greet(name){
yield "hello " + name + "!";
yield "I hope you are enjoying the generator course";
if(name.startsWith('Q')){
yield "it's cool how your name starts width Q, " + name;
}
yield "see you later!";
}
</script>
在谷歌浏览器中执行结果:
中间件(Middle)
实际上,koa有很多第三方开发的中间件,这些中间件的熟练运用才是关键,github 有很多这方面的库。以下是常用的一些: