为了让前端们从回调的地狱中回到天堂,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();
});