jQuery: Deferred Object
24 May 2017使用 Deferred Object 處理非同步狀況。
- Deferred 物件是可方法鏈結的(chainable)。
- 可註冊多個 callback 並放到 callback queue、啟動 callback queue、改變狀態以決定要進入成功或失敗的處理程序。
建立 Deferred 物件
Deferred 物件是由 $.Deferred()
建立的。
var $d = $.Deferred();
Deferred 物件的狀態
Deferred 物件的狀態有三種:resolved、rejected 和 pending。
- resolved:已解決。
- rejected:已拒絕。
- pending:未解決,表示仍在進行中的狀態,意即未回傳 resolved 或 rejected。
備註:若回傳非 Promise 或 Deferred 物件,則會視為 resolved 而進入 done()
。
狀態檢測
可使用 $d.state()
確認 Deferred 物件的狀態。
如下例,由於 getUserProfile()
會在三秒後使用 resolve
更新 $d
的狀態為 resolved,因此在 done()
中檢測狀態可得到 resolved。
程式碼。
var $d = $.Deferred();
function getUserProfile() {
setTimeout(function() {
$d.resolve({
id: '123456789',
name: 'Peter Wang',
email: 'sample@test.com',
});
}, 3000);
return $d.promise();
}
getUserProfile().done(function(res) {
console.log($d.state()); // resolved
});
Demo。
resolve
更改 Deferred 物件狀態的 method 有 resolve()
與 reject()
。
若成功取得資訊,則使用 resolve()
更新狀態為 resolved,並可回傳資訊,然後在 done()
接到結果。如下例,三秒後顯示訊息「Hi, Peter Wang」。
程式碼。
function getUserProfile() {
var $d = $.Deferred();
setTimeout(function() {
$d.resolve({
id: '123456789',
name: 'Peter Wang',
email: 'sample@test.com',
});
}, 3000);
return $d.promise();
}
getUserProfile().done(function(res) {
console.log('Hi, ' + res.name); // Hi, Peter Wang
});
Demo。
reject
若取得資訊失敗,則使用 reject()
更新狀態為 rejected,並可回傳錯誤訊息,然後在 fail()
接到結果。如下例,三秒後顯示訊息「Get User Profile Error :(」。
程式碼。
function getUserProfile() {
var $d = $.Deferred();
setTimeout(function() {
$d.reject({
msg: 'Get User Profile Error :(',
});
}, 3000);
return $d.promise();
}
getUserProfile().fail(function(res) {
console.log(res.msg); // Get User Profile Error :(
});
Demo。
nofity 與 progress
當 Deferred 物件尚未設定為 resolved 或 rejected 狀態,表示仍在進行中,則可使用 notify()
回報目前的執行狀況。如下例,每 0.5 秒印出目前 counter 的數字,直到 3 停止。
程式碼。
function reportProgress() {
var $d = $.Deferred();
var count = 0;
var intervalId = setInterval(function() {
$d.notify(count++);
count > 3 && clearInterval(intervalId);
}, 500);
return $d.promise();
}
var promise = reportProgress();
promise.progress(function(prog) {
console.log(prog);
});
Demo。
then
將 done()
、fail()
和 progress()
的 callback 合併至 then()
。
getUserProfile().then(
function(res) {
console.log('done');
},
function(res) {
console.log('fail');
},
function() {
console.log('still in progress...');
},
);
promise
使用 $d.promise()
來回傳 Deferred 物件狀態。如下所示,回傳 $d 為 resolved 狀態。
var $d = $.Deferred();
function getUserProfile() {
$d.resolve({
// ...
});
return $d.promise();
}
pipe
$d.pipe
屬於工具型的 method,用來串聯(and / or)的 Deferred 物件。
如下例,將 name 這個欄位後面加上「++」字串。
var $d = $.Deferred();
var filtered = $d.pipe(function(value) {
return $.extend({}, value, { name: value.name + '++' });
});
function getUserProfile() {
setTimeout(function() {
$d.resolve({
id: '123456789',
name: 'Peter Wang',
email: 'sample@test.com',
});
}, 500);
return $d.promise();
}
getUserProfile();
filtered.done(function(res) {
console.log(res);
});
when
使用 $.when()
來合併多個 Deferred 物件,任一回傳 rejected 就會進入 fail。如下例,等待 f1、f2 和 f3 執行完成後,印出執行結果。
function f1() {
var $d = $.Deferred();
setTimeout(function() {
$d.resolve('f1 done');
}, 1000);
return $d.promise();
}
function f2() {
var $d = $.Deferred();
setTimeout(function() {
$d.resolve('f2 done');
}, 3000);
return $d.promise();
}
function f3() {
var $d = $.Deferred();
setTimeout(function() {
$d.resolve('f3 done');
}, 2000);
return $d.promise();
}
$.when(f1(), f2(), f3()).done(function(res1, res2, res3) {
console.log(res1);
console.log(res2);
console.log(res3);
});
任一回傳 rejected 就會進入 fail。如下例, f2 回傳狀態 rejected(已拒絕)。
function f2() {
var $d = $.Deferred();
setTimeout(function() {
$d.reject('f2 done');
}, 3000);
return $d.promise();
}
$.when(f1(), f2(), f3()).fail(function(res1, res2, res3) {
console.log(res1);
console.log(res2);
console.log(res3);
});
Callback
done
當 Deferred 物件狀態為 resolved 時的處理 handler,可加入多個 callback。
程式碼。
function getUserProfile() {
var $d = $.Deferred();
setTimeout(function() {
$d.resolve({
id: '123456789',
name: 'Peter Wang',
email: 'sample@test.com',
});
}, 3000);
return $d.promise();
}
function f1() {
console.log('f1 done');
}
function f2() {
console.log('f2 done');
}
function f3(res) {
console.log('f3 done with ' + res.name);
}
getUserProfile()
.done(f1, f2)
.done(function(res) {
f3(res);
});
Demo。
fail
當 Deferred 物件狀態為 rejected 時的處理 handler。
always
無論 Deferred 物件狀態為 resolved 或 rejected,都會執行這個 handler。
程式碼。
getUserProfile()
.done(function(res) {
console.log('Get User Profile Success!');
})
.fail(function(res) {
console.log('Get User Profile Error :(');
})
.always(function() {
console.log('Always execute this line!');
});
Demo。
成功。
失敗。