//看到这个问题马上打开家里的windows去拖了个sublime来怒答
先谈理想,哦不,概念
首先defered模式不是伪promise,而是promise的一个变种。promise的初版Promise/A只有一个必备API then,后来的Promise/B已经具备了defer方法,虽然jQuery的实现细节与其不同,但基本意图是一样的(可以将promise的resolve这件事同样以方法调用的形式解决)
Promise是一个复杂的大家族,jQuery虽然曾经离经叛道,但自从pipe方法被舍弃,归并入then之后,已经是一个相当完整的Promise实现了。并不100%,但如果不考虑一些特殊场景(后详)下,jQuery的实现已经是Promise/A的超集。
再谈实际情况
Ajax业务错误是最最典型的超级适合用Promise解决问题的场景了,因为Promise的then方法有改变promise状态的能力,也就是标准的这句话
The value returned from the callback handler is the fulfillment value for the returned promise.
在更准确一些的Promise A+中,相应的描述也更明确易懂
then must return a promise [3.3].
promise2 = promise1.then(onFulfilled, onRejected);
If either onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure [[Resolve]](promise2, x).
If either onFulfilled or onRejected throws an exception e, promise2 must be rejected with e as the reason.
If onFulfilled is not a function and promise1 is fulfilled, promise2 must be fulfilled with the same value as promise1.
If onRejected is not a function and promise1 is rejected, promise2 must be rejected with the same reason as promise1.
换句人话,哦不,中文来说,想要改变promise的对象的状态,只要在then的参数回调当中返回相应的值,普通值代表成功,throw代表失败,另一个promise代表透传
jQuery的实现和标准略有区别,首先throw不会改变promise为失败而是直接就throw出去了,然后在失败的分支里返回非promise的值X的时候,最终promise的状态不是【成功,值X】而是【失败,值X】
重新换句人话,就是jQuery里想要扭转promise的状态就只能依靠返回另一种状态的promise对象。
上实际代码
jsfunction ajaxGet(url, params) {
return $.get(url, params)
.then(function(response) {
var obj;
try {
obj = $.parseJSON(response);
} catch(e) {
return reject({
state: 123,//比如说123代表返回结果非json
response: response,
error: e
});
}
if(obj.state !== 10000) {
return reject(obj);//直接把整个响应丢出去,如果约定了错误格式,可以在这里整理一下msg之类
}
return response;//成功! 如果约定了成功格式,也可以在这里转换
}, function(xhr) {//这是答主擅自追加的特性,可以不要
//网络错误
return reject({
state: 456,//比如说456代表网络错误
xhr: xhr//这是jqXHR对象,里面有错误详情
});
});
}
function reject(reason) {//构造处在错误状态的promise对象
var dfr = $.Deferred();
dfr.reject(reason);
return dfr.promise();
}
答主的习惯是在这个封装里顺便再给后台一个特殊的状态,比如state===789代表跳转,然后if(obj.state===789) window.location=obj.url;这样,顺便搞定了ajax捅出去以后发现未登录时跳转登录页等等各种需求,都是业务代码不用管的。另外会统一把错误整理成一句话放在比如msg字段里面, 很多时候业务可以在错误的时候直接.fail(function(err) {alert(err.msg || '未知错误')})完事儿
怒答了好久……
最后如果还意犹未尽的同学,不妨看看这几个issue,描述了三年前,chai-as-promise的作者和jquery中相关模块的作者及其他小伙伴们相爱相杀的曲折经历
最后的结局是喜闻乐见的一个困难的决定
if (typeof assertion._obj.pipe === "function") {
throw new TypeError("Chai as Promised is incompatible with jQuery's so-called “promises.” Sorry!");
}
幸运的是至少把pipe改名为then这一点还是顺利合入了jQuery1.7,这样看来事情还是一个不错的结局。否则SegmentFault今天还会多100个『jQuery pipe then和Promise的关系』之类的问题,github上还会多很多让jQuery更像Promise的第三方库,甚至连 Promise还能不能进入ES的草案都成疑问