Ember 異步路由

2018-01-06 17:50 更新

本文將為你介紹路由的高級特性,這些高級特性可以用于處理項目復(fù)雜的異步邏輯。

關(guān)于單詞promises,直譯是承諾,但是個人覺得還是使用原文吧。讀起來順暢點。

1,promises(承諾)

Ember的路由處理異步邏輯的方式是使用Promise。簡而言之,Promise就是一個表示最終結(jié)果的對象。這個對象可能是fulfill(成功獲取最終結(jié)果)也可能是reject(獲取結(jié)果失?。榱双@取這個最終值,或者是處理Promise失敗的情況都可以使用then方法,這個方法接受兩個可選的回調(diào)方法,一個是Promise獲取結(jié)果成功時執(zhí)行,一個是Promise獲取結(jié)果失敗時執(zhí)行。如果promises獲取結(jié)果成功那么獲取到的結(jié)果將作為成功時執(zhí)行的回調(diào)方法的參數(shù)。相反的,如果Promise獲取結(jié)果失敗,那么最終結(jié)果(失敗的原因)將作為Promise失敗時執(zhí)行的回調(diào)方法的參數(shù)。比如下面的代碼段,當(dāng)Promise獲取結(jié)果成功時執(zhí)行fulfill回調(diào),否則執(zhí)行reject回調(diào)方法。

//  app/routes/promises.js


import Ember from 'ember';


export default Ember.Route.extend({
    beforeModel: function() {
        console.log('execute model()');


        var promise = this.fetchTheAnswer();
        promise.then(this.fulfill, this.reject);
    },


    //  promises獲取結(jié)果成功時執(zhí)行
    fulfill: function(answer) {
      console.log("The answer is " + answer);
    },


    //  promises獲取結(jié)果失敗時執(zhí)行
    reject: function(reason) {
      console.log("Couldn't get the answer! Reason: " + reason);
    },


    fetchTheAnswer: function() {
        return new Promise(function(fulfill, reject){
            return fulfill('success');  //如果返回的是fulfill則表示promises執(zhí)行成功
            //return reject('failure');  //如果返回的是reject則表示promises執(zhí)行失敗
        });
    }
});

上述這段代碼就是promises的一個簡單例子,promises的then方法會根據(jù)promises的獲取到的最終結(jié)果執(zhí)行不同的回調(diào),如果promises獲取結(jié)果成功則執(zhí)行fulfill回調(diào),否則執(zhí)行reject回調(diào)。

promises的強(qiáng)大之處不僅僅如此,promises還可以以鏈的形式執(zhí)行多個then方法,每個then方法都會根據(jù)promises的結(jié)果執(zhí)行fulfill或者reject回調(diào)。

//  app/routes/promises.js


import Ember from 'ember';


export default Ember.Route.extend({


    beforeModel() {
        // 注意Jquery的Ajax方法返回的也是promises
        var promiese = Ember.$.getJSON('https://api.github.com/repos/emberjs/ember.js/pulls');
        promiese.then(this.fetchPhotoOfUsers)
                .then(this.applyInstagramFilters)
                .then(this.uploadThrendyPhotAlbum)
                .then(this.displaySuccessMessage, this.handleErrors);


    },
    fetchPhotoOfUsers: function(){
        console.log('fetchPhotoOfUsers');
    },
    applyInstagramFilters: function() {
        console.log('applyInstagramFilters');
    },
    uploadThrendyPhotAlbum: function() {
        console.log('uploadThrendyPhotAlbum');
    },
    displaySuccessMessage: function() {
        console.log('displaySuccessMessage');
    },
    handleErrors: function() {
        console.log('handleErrors');
    }
});

這種情況下會打印什么結(jié)果呢??

在前的文章已經(jīng)使用過Ember.$.getJSON('https://api.github.com/repos/emberjs/ember.js/pulls');獲取數(shù)據(jù),是可以成功獲取數(shù)據(jù)的。所以promises獲取結(jié)果成功,應(yīng)該執(zhí)行的是獲取成功對應(yīng)的回調(diào)方法。瀏覽器控制臺打印結(jié)果如下:

fetchPhotoOfUsers
applyInstagramFilters
uploadThrendyPhotAlbum
displaySuccessMessage

但是如果我把Ember.$.getJSON('https://api.github.com/repos/emberjs/ember.js/pulls');改成一個不存在的URL,比如改成Ember.$.getJSON('https://www.my-example.com');執(zhí)行代碼之后控制臺會提示出404錯誤,并且打印'handleErrors'。說明promises獲取結(jié)果失敗,執(zhí)行了then里的reject回調(diào)。為了驗證每個回調(diào)的reject方法再修改修改代碼,如下:

//  app/routes/promises.js


import Ember from 'ember';


export default Ember.Route.extend({


    beforeModel() {
        // 注意Jquery的Ajax方法返回的也是promises
        var promiese = Ember.$.getJSON(' https://www.my-example.com ');
        promiese.then(this.fetchPhotoOfUsers, this.fetchPhotoOfUsersError)
                .then(this.applyInstagramFilters, this.applyInstagramFiltersError)
                .then(this.uploadThrendyPhotAlbum, this.uploadThrendyPhotAlbumError)
                .then(this.displaySuccessMessage, this.handleErrors);


    },
    fetchPhotoOfUsers: function(){
        console.log('fetchPhotoOfUsers');
    },
    fetchPhotoOfUsersError: function() {
        console.log('fetchPhotoOfUsersError');
    },
    applyInstagramFilters: function() {
        console.log('applyInstagramFilters');
    },
    applyInstagramFiltersError: function() {
        console.log('applyInstagramFiltersError');
    },
    uploadThrendyPhotAlbum: function() {
        console.log('uploadThrendyPhotAlbum');
    },
    uploadThrendyPhotAlbumError: function() {
        console.log('uploadThrendyPhotAlbumError');
    },
    displaySuccessMessage: function() {
        console.log('displaySuccessMessage');
    },
    handleErrors: function() {
        console.log('handleErrors');
    }
});

result

由于promises獲取結(jié)果失敗故執(zhí)行其對應(yīng)的失敗處理回調(diào)。這種調(diào)用方式有點類似于try……catch……,但是本文的重點不是講解promises,更多有關(guān)promises的教材請讀者自行Google或者百度吧,在這里介紹一個js庫RSVP.js,它可以讓你更加簡單的組織你的promises代碼。 在附上幾個promises的參考網(wǎng)站:

  1. https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise
  2. http://liubin.github.io/promises-book/
  3. http://www.zhangxinxu.com/wordpress/2014/02/es6-javascript-promise-%E6%84%9F%E6%80%A7%E8%AE%A4%E7%9F%A5/

極力推薦看第二個網(wǎng)站的教材,這個網(wǎng)站可以直接運行js代碼。還有源碼和PDF。非常棒!??!

2,promises中止路由

當(dāng)發(fā)生路由切換的時候,在model回調(diào)(或者是beforeMode、afterModel)中獲取的數(shù)據(jù)集會在切換完成的時候傳遞到路由對應(yīng)的controller上。如果model回調(diào)返回的是一個普通的對象(非promises對象)或者是數(shù)組,路由的切換會立即執(zhí)行,但是如果model回調(diào)返回的是一個promises對象,路由的切換將會被中止直到promises執(zhí)行完成(返回fulfill或者是reject)才切換。

路由器任務(wù)任何一個包含了then方法的對象都是一個promises。

如果promises獲取結(jié)果成功則會從被中止的地方繼續(xù)往下執(zhí)行或者是執(zhí)行路由鏈的下一個路由,如果promises返回的依然是一個promises,那么路由依然再次被中止,等待promises的返回結(jié)果,如果是fulfill則從被中止的地方開始往下執(zhí)行,以此類推,一直到獲取到model回調(diào)所需的結(jié)果。

傳遞到每個路由的setupController回調(diào)的值都是promises返回fulfill時的值。如下代碼:

//  app/routes/tardy.js


import Ember from 'ember';


export default Ember.Route.extend({
    model: function() {
        return new Ember.RSVP.Promise(function(resolver) {
            console.log('start......');
            Ember.run.later(function() {
                resolver({ msg: 'Hold your horses!!'});
            }, 3000);
        });
    },
    setupController(controller, model) {
        console.log('msg = ' + model.msg);
    }
});

一進(jìn)入路由tardymodel回調(diào)就會被執(zhí)行并且返回一個延遲3秒才執(zhí)行的promises,在這期間路由會中止。當(dāng)promises返回fulfill路由會繼續(xù)執(zhí)行,并將model返回的對象傳遞到setupController方法中。

雖然這種中止的行為會影響響應(yīng)速度但是這是非常必要的,特別是你需要保證model回調(diào)得到的數(shù)據(jù)是完整的數(shù)據(jù)的時候。

3,promises獲取結(jié)果失敗

文章前面主要講的是promises獲取結(jié)果成功的情況,但是如果是獲取結(jié)果失敗的情況又是怎么處理呢??

默認(rèn)情況下,如果model回調(diào)返回的是一個promises對象并且此promises返回的是reject,此時路由切換將被終止,也不會渲染對應(yīng)的模板,并且會在瀏覽器控制臺打印出錯誤日志信息,例子promises-ret-reject.js會演示。

你可以自定義處理出錯信息的邏輯,只要在routeactions哈希對象中配置即可。當(dāng)promises獲取結(jié)果失敗的默認(rèn)情況下會執(zhí)行一個名為error的處理事件,否則會執(zhí)行你自定義的處理事件。

//  app/routes/promises-ret-reject.js


import Ember from 'ember';


export default Ember.Route.extend({
    model: function() {
        //  為了測試效果直接返回reject
        return Ember.RSVP.reject('FAIL');
    },
    actions: {
        error: function(reason) {
            console.log('reason = ' + reason);


            //  如果你想讓這個事件冒泡到頂級路由application只需要返回true
            //  return true;
        }
    }
});

如果沒有不允許事件冒泡打印結(jié)果僅僅是reason = FAIL。并且頁面上什么都不顯示(不渲染模板)。

如果去掉最后一行代碼的注釋,讓事件冒泡到頂級路由application中的默認(rèn)方法處理,那么結(jié)果又是什么呢?

result

結(jié)果是先打印了處理結(jié)果,然后再打印出提示錯誤的日志信息。并且頁面上什么都不顯示(不渲染模板)。

4,恢復(fù)promises的reject狀態(tài)

在前面第3點介紹了promises獲取結(jié)果失敗時會終止路由轉(zhuǎn)換,但是如果model返回是一個promises鏈呢?程序能到就這樣死了?。?!顯然是不行的,做法是把model回調(diào)中返回的reject轉(zhuǎn)換為fulfill。這樣就可以繼續(xù)執(zhí)行或者切換到下一個路由了!

//  app/routes/funky.js


import Ember from 'ember';


export default Ember.Route.extend({
    model: function() {
        var promises = Ember.RSVP.reject('FAIL');
        //  由于已經(jīng)知道promises返回的是reject,所以fulfill回調(diào)直接寫為null
        return promises.then(null, function() {
            return { msg: '恢復(fù)reject狀態(tài):其實就是在reject回調(diào)中繼續(xù)執(zhí)行fulfill狀態(tài)下的代碼。' };
        });
    }
});

為了驗證model回調(diào)的結(jié)果,直接在模板上顯示msg。





funky模板
<br>
{{model.msg}}

執(zhí)行URL:http://localhost:4200/funky,得到如下結(jié)果:

result

說明model回調(diào)進(jìn)入到reject回調(diào)中,并正確返回了預(yù)期結(jié)果。

到本文為止有關(guān)路由這以整章的內(nèi)容也全部介紹完畢了??!難點在Ember.js 入門指南之二十六查詢參數(shù)這一篇。能力有限沒有把這篇的內(nèi)容講明白,暫時擱下待日后完善!

總的來說路由主要職責(zé)是獲取數(shù)據(jù),根據(jù)邏輯處理數(shù)據(jù)。有點MVC架構(gòu)的dao層,專門做數(shù)據(jù)的CRUD操作。當(dāng)然另外一個重要職責(zé)就是路由的切換,以及切換的時候參數(shù)的設(shè)置問題。

結(jié)束完這一章下一章接著介紹組件(Component)。


博文完整代碼放在Github(博文經(jīng)過多次修改,博文上的代碼與github代碼可能又出入,不過影響不大!),如果你覺得博文對你有點用,請在github項目上給我點個star吧。您的肯定對我來說是最大的動力!!

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號