JavaScript 异步编程开源库

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("已执行!"); });

来源:jQuery的deferred对象详解

<br />

2.Q.js

Q.js 是知名、功能完整的 Promise 函式库,已经实现了 Promise/A 标准。
特点:

标准的回调函数的方式如下,嵌套比较深:

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 有很多这方面的库。以下是常用的一些:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • jQuery的deferred对象详解 作者:阮一峰 一、什么是deferred对象? 开发网站的过程中,我们经常...
    JamHsiao_aaa4阅读 308评论 0 0
  • Promise 的含义 一句话概括一下promise的作用:可以将异步操作以同步操作的流程表达出来,避免了层层嵌套...
    雪萌萌萌阅读 5,320评论 0 7
  • 一、什么是deferred对象? 开发网站的过程中,我们经常遇到某些耗时很长的javascript操作。其中,既有...
    壮哉我大前端阅读 250评论 0 1
  • 1、什么是deferred对象 开发网站的过程中,我们经常遇到某些耗时很长的javascript操作。其中,既有异...
    公子七阅读 239评论 1 3
  • 跟几位老师听课回来。路上说起修行跟插座一样,时而通时而断,一天中大部分时间都为烦恼所转,想不起修行。问我怎么办。 ...
    索訶阅读 489评论 0 0