Update 2016/04/05 20:27 話說 beta 中的 jQuery 3.0 確定 Promise 的部分會是相容於 Promises/A+,到時候出了正式版本可以開心升級!
其實不要用 jQuery promise 這回事兒,時有所聞,最主要的原因是其實它實作的內容跟外面大部分的 Promise library (Promises/A+)都不一樣。
所以很多人覺得 jQuery 的 Promise 只是名為 Promise 的「某種東西」,並不是真正的 Promise。
想了解詳情可以看一下 2012 年的這篇文章 You’re Missing the Point of Promises
然而我一直不以為意。
到這個星期我才切身的實際體會不要用的原因,這邊列出三點,應該是比較容易碰到的情況。
1. 面對靜態結果,$.when() 不會以非同步的方式產出 resolved promise
這個我想應該很多人已經有經驗 XD
舉例如下:
// 期待中,結果會是以 async 的方式取得
function xhrGet(something) {
if (cache) {
return $.when(cache);
}
return $.get(something);
}
/**
* 在送出 XHR request 之後清空頁面,取得 response 之後重繪頁面
*
* 沒有 cache - 正常運作
* 有 cache - 在 `renderPage()` 之後才 `clearPage()`,頁面就沒了
*/
xhrGet(...).then(function (res) {
renderPage(res);
});
clearPage();
那麼我們實際來確認一下大家的情況,下面用了三家的 library 來做測試:
(其中 Promise
的部分是用 polyfill 的方式,如果你的瀏覽器有原生支援就會用瀏覽器內建的)
// $.when
log('# $.when()');
log(1);
$.when(true).then(function () {
log('2 async [jquery]');
});
log(3);
log('\n');
// Promise.resolve
log('# Promise.resolve()');
log(1);
Promise.resolve(true).then(function () {
log('2 async [Promise]');
});
log(3);
log('\n');
// Q.when
log('# Q()');
log(1);
Q(true).then(function () {
log('2 async [Q]');
});
log(3);
範例擺在 Plunker 上:
然而如此微小的挫折並不足以澆熄我熱愛 jQuery Promise 的決心…
2. jQuery Promise 跟原生 Promise 或其他 Promises/A+ library 的相容性不好
如果是 jQuery Promise 剛出來的那個年代(jQuery 1.5),應該很難碰到這個 case,然而現在是原生 Promise 大放異彩的年代,避的了一時,躲不了一世啦~
這週碰巧用了 Mozilla 開源的 storage library localForage,因為它基本上是包裝 IndexedDB 或 WebSQL 成 key-value-pair 的使用方法,所以大部分的操作 API 都是非同步的;而非同步的部分,可以選擇用 callback 或是 Promise 來解決。
想當然而我選了 Promise ,配上 jQuery Promise 之後下面的範例就沒辦法直覺地正常運作。
var TEST_KEY = 'test123',
TEST_VALUE = 'ooxx...';
function getData () {
return localforage.setItem(TEST_KEY, TEST_VALUE)
.then(function () {
return localforage.getItem(TEST_KEY);
});
}
$.when(getData())
.then(function (res) {
// 期待的結果是: jquery ooxx...
console.log('jquery', res);
});
然而在 Promise 或 Q 裡面這樣寫是會得到如預期的結果的喔!
這是一個延伸的綜合範例,一樣是比較在第一點裡面的三個實作,有興趣的話,點過去之後打開 console 看看。
3. 處理 reject 的方式跟大家不一樣
試問下面的執行結果應該是什麼呢?
$.Deferred().reject()
.then(null, function () {
return 'error handled';
})
.then(function (res) {
log('jquery resolve', res);
}, function (res) {
log('jquery reject', res);
});
// output: jquery reject error handled
promise chain 中被 reject,經過 rejection handler 後
- jQuery: 保持 rejected (除非在 rejection handler 裡面回傳一個 resolved promise)
- Promises/A+: 轉為 resolved
其實這個理由可能本文提到的三種之中最崩潰的…
結論
其實要不要棄用 jQuery Promise 還是見仁見智,如果你已經處理好了 static resolve 的 async 問題(即 第 1 點 的情況),並且又確定不會需要跟別的 Promise library 混用的話,只用 jQuery Promise 還是可以過得好好的(不過還是要注意 behavior 不同的部分,比如 第 3 點)。
不過在現在這個前後端JS爆炸並且 module 化的時代,我還是誠心推薦你做個轉換吧!
你說那那些原本就 return jQuery Promise 的 jQuery API 怎麼辦?
其實轉換 jQuery Promise 的最簡單方法就是直接用 Promise 把它包起來(resolve)就好囉:
// 以 $.get() 為例
// Promise
Promise.resolve($.get(...)).then(...)
// Q
Q($.get(...)).then(...)
更詳細資訊可以看看 Q 的文件裡精心製作的 migration guide。
想當年我有一篇 Promise 相關的文章也是用 jQuery 寫的(遠目)