英文原版:https://guides.emberjs.com/v2.13.0/routing/asynchronous-routing/
本節涵蓋了一些router的高級功能,即異步邏輯處理能力。
聊聊Promises
Ember的router在處理異步邏輯的時候大量使用了Promise。簡而言之,promise是表示最終結果的對象。一個promise可以被完成(即 resolve)或被拒絕( 即 reject )。處理完成和拒絕狀態的方式是通過promise的then()函數,它接受2個函數爲參數,一個爲當完成promise時被調用,另一個在promise被拒絕是被調用。當promise被完成時,第一個函數將被調用,並且結果作爲唯一的參數被傳入函數;當promise被拒絕時,第二個函數被調用,並且被拒絕的原因作爲唯一的參數被傳入函數。看下例:
let promise = fetchTheAnswer();
promise.then(fulfill, reject);
function fulfill(answer) {
console.log(`The answer is ${answer}`);
}
function reject(reason) {
console.log(`Couldn't get the answer! Reason: ${reason}`);
}
實際上promise的主要影響力是可以通過鏈式調用的方式來按順序執行異步操作,即 異步操作同步化:
// Note: jQuery AJAX methods return promises
let usernamesPromise = Ember.$.getJSON('/usernames.json');
usernamesPromise.then(fetchPhotosOfUsers)
.then(applyInstagramFilters)
.then(uploadTrendyPhotoAlbum)
.then(displaySuccessMessage, handleErrors);
在上面的例子中,如果當中的任何一個回調函數:fetchPhotosOfUsers、applyInstagramFilters、uploadTrendyPhotoAlbum 返回了一個被拒絕的結果,handleErrors 就會被調用。通過這種方式, promise變成了類似於try…catch語句結構的異步操作,同時終結了傳統的內層函數要不斷的右縮進的煩惱。並且可以更有條理的管理複雜的異步邏輯。
本教程不會深入討論promise的各種用法,但是如果你想要了解更多,請閱讀RSVP, 這個是Ember的promise庫。
因Promise而暫停
當你在路由間穿梭時,Ember router會蒐集所有的model數據,並在transtion即將結束時把它們傳入路由的controller中。如果model( )鉤子(也包含 beforeModel和afterModel)返回了正常的對象(非promise)或數組,本次transition會立即完成。但是如果model( )鉤子(也包含 beforeModel和afterModel)返回了一個promise(或者promise作爲transitionTo( )的參數),那麼本次transition會在promise返回一個值( 無論 resolve 還是 reject )之前一直暫停。
router會把任何帶有then()函數的對象當做是promise。
如果promise完成,那麼transition會從它停止的地方繼續執行,並且會繼續執行子路由,如果子路由也有promise,那麼繼續暫停,直到最終執行到最末端的路由。並且每層路由都會調用它的setupController()鉤子,並將promise完成時的返回值作爲參數傳入。
一個基本的例子:
app/routes/tardy.js
import Ember from 'ember';
import RSVP from 'rsvp';
export default Ember.Route.extend({
model() {
return new RSVP.Promise(function(resolve) {
Ember.run.later(function() {
resolve({ msg: 'Hold Your Horses' });
}, 3000);
});
},
setupController(controller, model) {
console.log(model.msg); // "Hold Your Horses"
}
});
當訪問tardy路由時,model( )鉤子將被調用並且返回一個promise,這個promise會在3s後被resolve,在這期間router將會暫停。當promise最終被resolve,router會繼續執行並最終調用setupController(),並傳入被resolve的對象。
When Promise Reject…
我們已經介紹了當promise被resolve時的情況,how about if it rejects?
默認的,當promise被reject時,當前的transition會被終止,不會有新的目標模板被渲染,並且一條錯誤日誌會被顯示在控制檯。
你可以通過error( ) action函數來處理這段邏輯。當promise被reject後,會在路由中觸發一個error事件,並且如果沒有在沿途的路由中定出error處理函數,那麼這個事件會一直冒泡到application路由:
app/routes/good-for-nothing.js
import Ember from 'ember';
import RSVP from 'rsvp';
export default Ember.Route.extend({
model() {
return RSVP.reject("FAIL");
},
actions: {
error(reason) {
alert(reason); // "FAIL"
// Can transition to another route here, e.g.
// this.transitionTo('index');
// Uncomment the line below to bubble this error event:
// return true;
}
}
});
在上面的例子中,error事件會在error處理函數執行完後終結,並且不會冒泡。如果你想要讓error事件冒泡,在error處理函數中返回 true 。
從reject中恢復
promise被reject會導致當前的transition終止,不過由於promise是可以鏈式調用的,所以你可以model( )中捕獲到被reject的promise並且將結果反轉爲完成的,並且會繼續當前的transition:
app/routes/funky.js
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return iHopeThisWorks().catch(function() {
// Promise rejected, fulfill with some default value to
// use as the route's model and continue on with the transition
return { msg: 'Recovered from rejected promise' };
});
}
});
本章完