异步操作的模式

1.回调函数

  1. function f1(callback) {
  2. // ...
  3. callback();
  4. }
  5. function f2() {
  6. // ...
  7. }
  8. f1(f2);

回调函数的优点是简单、容易理解和实现,缺点是不利于代码的阅读和维护,各个部分之间高度耦合(coupling),使得程序结构混乱、流程难以追踪(尤其是多个回调函数嵌套的情况),而且每个任务只能指定一个回调函数。

2.事件监听

  1. f1.on('done', f2);
  2. function f1() {
  3. setTimeout(function () {
  4. // ...
  5. f1.trigger('done');
  6. }, 1000);
  7. }

这种方法的优点是比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以“去耦合”(decoupling),有利于实现模块化。缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰。阅读代码的时候,很难看出主流程。

3.发布/订阅

事件完全可以理解成“信号”,如果存在一个“信号中心”,某个任务执行完成,就向信号中心“发布”(publish)一个信号,其他任务可以向信号中心“订阅”(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做”发布/订阅模式”(publish-subscribe pattern),又称“观察者模式”(observer pattern)。

  1. jQuery.subscribe('done', f2);
  2. function f1() {
  3. setTimeout(function () {
  4. // ...
  5. jQuery.publish('done');
  6. }, 1000);
  7. }

这种方法的性质与“事件监听”类似,但是明显优于后者。因为可以通过查看“消息中心”,了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行。

异步操作的流程控制

  1. 串行执行
  2. 并行执行;相比而言,上面的写法只要一秒,就能完成整个脚本。这就是说,并行执行的效率较高,比起串行执行一次只能执行一个任务,较为节约时间。但是问题在于如果并行的任务较多,很容易耗尽系统资源,拖慢运行速度。因此有了第三种流程控制方式。
  3. 并行与串行的结合;所谓并行与串行的结合,就是设置一个门槛,每次最多只能并行执行n个异步任务,这样就避免了过分占用系统资源。

setTimeout

setTimeout函数用来指定某个函数或某段代码,在多少毫秒之后执行。它返回一个整数,表示定时器的编号,以后可以用来取消这个定时器。

除了前两个参数,setTimeout还允许更多的参数。它们将依次传入推迟执行的函数(回调函数)。

还有一个需要注意的地方,如果回调函数是对象的方法,那么setTimeout使得方法内部的this关键字指向全局环境,而不是定义时所在的那个对象。

  1. var x = 1;
  2. var obj = {
  3. x: 2,
  4. y: function () {
  5. console.log(this.x);
  6. }
  7. };
  8. setTimeout(obj.y, 1000) // 1

为了防止出现这个问题,一种解决方法是将obj.y放入一个函数:setTimeout(function () {obj.y();}, 1000);

这使得obj.y在obj的作用域执行,而不是在全局作用域内执行,所以能够显示正确的值。

另一种解决方法是,使用bind方法,将obj.y这个方法绑定在obj上面。setTimeout(obj.y.bind(obj), 1000)

setInterval

用法基本一致

为了确保两次执行之间有固定的间隔,可以不用setInterval,而是每次执行结束后,使用setTimeout指定下一次执行的具体时间。

clearTimeout(),clearInterval()

setTimeout和setInterval函数,都返回一个整数值,表示计数器编号。将该整数传入clearTimeout和clearInterval函数,就可以取消对应的定时器。

debounce 防抖动

  1. $('textarea').on('keydown', debounce(ajaxAction, 2500));
  2. function debounce(fn, delay){
  3. var timer = null; // 声明计时器
  4. return function() {
  5. var context = this;
  6. var args = arguments;
  7. clearTimeout(timer);
  8. timer = setTimeout(function () {
  9. fn.apply(context, args);
  10. }, delay);
  11. };
  12. }

Promise

Promise 对象是 JavaScript 的异步操作解决方案,为异步操作提供统一接口。它起到代理作用(proxy),充当异步操作与回调函数之间的中介,使得异步操作具备同步操作的接口。Promise 可以让异步操作写起来,就像在写同步操作的流程,而不必一层层地嵌套回调函数。

Promise 对象通过自身的状态,来控制异步操作。Promise 实例具有三种状态。

异步操作未完成(pending)
异步操作成功(fulfilled)
异步操作失败(rejected)
上面三种状态里面,fulfilled和rejected合在一起称为resolved(已定型)。

这三种的状态的变化途径只有两种。

从“未完成”到“成功”
从“未完成”到“失败”
一旦状态发生变化,就凝固了,不会再有新的状态变化。这也是 Promise 这个名字的由来,它的英语意思是“承诺”,一旦承诺成效,就不得再改变了。这也意味着,Promise 实例的状态变化只可能发生一次。

因此,Promise 的最终结果只有两种。

异步操作成功,Promise 实例传回一个值(value),状态变为fulfilled。
异步操作失败,Promise 实例抛出一个错误(error),状态变为rejected。