Wenzi

jQuery 的 promise

蚊子前端博客
发布于 2015/04/18 06:00
为了让前端们从回调的地狱中回到天堂,jQuery也引入了Promise的概念。

为了让前端们从回调的地狱中回到天堂,jQuery 也引入了 Promise 的概念。Promise 是一种令代码异步行为更加优雅的抽象,有了它,我们就可以像写同步代码一样去写异步代码。jQuery 从 1.5 版本开始实现了 CommonJS Promise/A 规范这一重量级方案,不过没有严格按照规范进行实现,有一些 API 上的差异。

1. 以前的 ajax #

1.1 原生的 ajax 写法 #

以前我们写 ajax 请求时,如果是用原生的 JavaScript 代码写呢,通常是这样的一种形式:

var XHR = (function () {
  var obj = null; // xhr对象
  function init() {
    create();
    send();
  }
  // 创建xhr对象
  function create() {
    if (window.XMLHttpRequest) {
      obj = new XMLHttpRequest();
    } else if (window.ActiveObject) {
      obj = new ActiveObject("Microsoft.XMLHTTP");
    }
    echange();
  }
  // 发送消息
  function send() {
    var v = parseInt(Math.random() * 100);
    obj.open("get", "test.php?v=" + v, true);
    obj.send(null);
    obj.onreadystatechange = echange;
  }
  // 接收消息
  function echange() {
    /*
        0 (未初始化) 对象已建立,但是尚未初始化(尚未调用open方法)
        1 (初始化) 对象已建立,尚未调用send方法
        2 (发送数据) send方法已调用,但是当前的状态及http头未知
        3 (数据传送中) 已接收部分数据,因为响应及http头不全,这时通过responseBody和responseText获取部分数据会出现错误
        4 (完成) 数据接收完毕,此时可以通过通过responseBody和responseText获取完整的回应数据
        */
    if (obj.readyState == 4 && obj.status == 200) {
      var result = JSON.parse(obj.responseText);
      console.log(result);
    }
  }
  return {
    init: init,
  };
})();

1.2 jQuery 的 ajax 写法 #

原生的 ajax 代码能比较清晰的看出 ajax 的流程,可是如果每次都写这么多的代码就会造成很多的不便。不过在 jQuery 中,已经对 ajax 的代码进行封装,使用 jQuery 的 ajax 时就不用写这么多的代码了。当然,我们自己也可以对原生的 ajax 代码进行封装。

$.ajax({
  url: "test.php", // 请求地址
  data: { v: v }, // 发送的数据
  dataType: "json", // 返回的数据格式
  type: "get", // 请求的方式:get|post

  // 发送前执行
  beforeSend: function () {
    console.log("beforeSend");
  },
  // 返回成功执行
  success: function (result) {
    console.log(result);
  },
  // 返回失败执行
  error: function (err) {
    console.log(err);
  },
  // 无论成功还是失败都会执行
  complete: function () {
    console.log("complete");
  },
  // 状态码对应的操作
  statusCode: {
    200: function () {
      console.log("200, ok");
    },
    404: function () {
      console.log("404, page not found");
    },
  },
});

上面的代码中虽然列了很多的选项,但是不是所有的都能用到的,可以结合实际的例子进行相应的取舍。比如 beforeSend, statusCode 等,如果实在是用不到,也可以不写的。

1.3 多个 ajax 顺序请求时 #

在使用这样的 ajax 进行多次顺序请求时,我们会采用嵌套的方式来进行。

// 首次ajax请求
$.ajax({
  url: "test.php",
  success: function () {
    // 请求完成后进行下次的请求
    $.ajax({
      url: "test1.php",
      success: function () {
        // 进行下次的ajax请求
        $.ajax({
          url: "test.php",
          success: function () {
            // ...
          },
        });
      },
    });
  },
});

如果有比较多的 ajax 请求时,我们发现这样的代码实在是不利于我们的阅读,而且造成以后代码的修改和维护很困难。那有没有一种写法,我们既能分开写 ajax 请求,又能顺序执行。

当然有啦,一个比较简单的方式就是使用异步回调的方式:

// 第一个ajax请求
function ajax(callback) {
  $.ajax({
    url: "test.php",
    success: function () {
      callback();
    },
  });
}
// 第二个ajax请求
function ajax1(callback) {
  $.ajax({
    url: "test.php",
    success: function () {
      callback();
    },
  });
}

// 将第二个ajax方法作为第一个方法的回调方法
// 如果有多个ajax请求时,可以调用多个回调方法
ajax(ajax1); // ajax(ajax1(ajax2));

2. 现在的 ajax #

Promise/Deferred 模式在 JavaScript 框架中最早出现于 Dojo 的代码中,被广为所知则来自于 jQuery 1.5 版本,该版本几乎重写了 ajax 部分,使得调用 ajax 时可以通过如下的形式进行:

$.ajax({ url: "test.php" })
  .success(function () {})
  .error(function () {})
  .complete(function () {});

这使得即使不调用 success(), error()等方法,ajax 也会执行,这样的调用方式比预先传入的回调让人觉得舒适一些。在原始的 API 中,一个事件只能处理一个回调,而通过 Deferred 对象,可以对事件加入任意的业务处理逻辑,示例代码如下:

$.ajax({ url: "test.php" })
  .success(function () {})
  .error(function () {})
  .success(function () {});

Promise/Deferred 模式在 2009 年时被 Kris Zyp 抽象为一个提议草案,发布在 CommonJS 规范中,随着使用 Promise/Deferred 模式的应用逐渐增多,CommonJS 草案目前已经抽象出了 Promise/A、 Promise/B、Promise/D 这样典型的异步 Promise/Deferred 模型,这使得异步操作可以以一种优雅的方式出现。

jQuery 的 1.5 版本之后,$.ajax()返回的就是 promise 对象,而且其他的 jQuery 对象也能通过调用 promise()返回一个 promise 对象。因此,上面的 ajax 可以写成这样:

var promise = $.ajax({ url: "test.php" });
promise.success(function () {
  // success
});
promise.error(function () {
  // error
});

不过在 1.8 版本之后,jQuery 已经不推荐 success(),error()和 complete()方法了,推荐使用 done(), fail()和 always(),分别对应着前面三个方法。修改如下:

var promise = $.ajax({ url: "test.php" });
promise.done(function () {
  // done
});
promise.fail(function () {
  // fail
});
promise.always(function () {
  // always
});

我们也可以用 then()方法把 done()和 fail()合并到一起。

promise.then(
  function () {
    // done
  },
  function () {
    // fail
  }
);

第一个参数表示 done 方法,第二个方法表示 fail 方法;如果只传递一个参数的话,就表示 done 方法。

同时在 jQuery 中还提供了$.when()方法

$.when(deferreds) : 提供一种方法来执行一个或多个对象的回调函数, Deferred(延迟)对象通常表示异步事件

如果向 jQuery.when() 传入一个延迟对象,那么会返回它的 Promise 对象(延迟方法的一个子集)。可以继续绑定 Promise 对象的其它方法,例如, defered.then 。当延迟对象已经被受理(resolved)或被拒绝(rejected)(通常是由创建延迟对象的最初代码执行的),那么就会调用适当的回调函数。

$.when($.ajax({ url: "test.php" })).then(function (data, status, jqXHR) {
  // done
  // console.log(data, status, jqXHR);
});

在案例中有多个延迟对象传递给 jQuery.when() ,该方法返回一个新的“宿主”延迟对象,跟踪所有已通过 Deferreds 聚集状态。 当所有的延迟对象被受理(resolve)时,该方法才会受理它的 master 延迟对象。当其中有一个延迟对象被拒绝(rejected)时,该方法就会拒绝它的 master 延迟对象。

如果 master 延迟对象被受理(resolved),那么会传入所有延迟对象的受理(resolved)值,这些延迟对象指的就是传给 jQuery.when 的参数。例如,当延迟对象是 jQuery.ajax() 请求,那么传入的受理(resolved)参数就是请求用的 jqXHR 对象,传入顺序就是它们在参数列表中的顺序。

在多延迟情况下,如果延迟一被拒绝,jQuery.when 触发立即调用 master 延迟对象的 failCallbacks。请注意,在上述情况中,有一些延迟对象依然是不能被受理(unresolved)的。那么,在这种情况下,如果需要执行一些额外的处理,例如,取消所有未完成的 ajax 请求,你可以在闭包中进行保持 jqXHR 对象的引用,并且在 failCallback 中检查或取消它们。

$.when($.ajax({ url: "test.php" }), $.ajax({ url: "test1.php" }))
  .done(function (result, result1) {
    console.log(result);
    console.log(result1);
  })
  .fail(function (err) {
    console.log("error");
  });

当两个 ajax 都执行成功时就会调用 done 方法,否则只要其中一个失败就会执行 fail 方法。

3. promise 对象使用的其他场景 #

promise 对象不只是能在 ajax 中使用,在其他有时间延迟的地方也能够使用,比如animate(), fadeIn(), fadeOut() 等,我们想在 fadeIn()之后执行 animate(),然后执行 fadeOut 让他隐藏。

一种方法就是使用这个方法提供的回调函数,来确保它的先后顺序:

<style type="text/css">
  .process {
    background: #ccc;
    display: none;
    width: 150px;
    height: 150px;
  }
</style>
<body>
  <div class="process"></div>
</body>
<script>
  // 记得加载jQuery库
  $(".process").fadeIn(function () {
    $(this).animate({ width: "300px", height: "300px" }, 1000, function () {
      $(this).fadeOut();
    });
  });
</script>

还有就是用上面讲到的 promise 对象。

$(".process")
  .fadeIn()
  .promise()
  .then(function () {
    $(this).animate({ width: "300px", height: "300px" }, 1000);
  })
  .then(function () {
    $(this).fadeOut();
  });
阅读(2228)
Simple Empty
No data