Ember 異步路由

2018-01-06 17:50 更新

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

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

1,promises(承諾)

Ember的路由處理異步邏輯的方式是使用Promise。簡而言之,Promise就是一個(gè)表示最終結(jié)果的對象。這個(gè)對象可能是fulfill(成功獲取最終結(jié)果)也可能是reject(獲取結(jié)果失?。?。為了獲取這個(gè)最終值,或者是處理Promise失敗的情況都可以使用then方法,這個(gè)方法接受兩個(gè)可選的回調(diào)方法,一個(gè)是Promise獲取結(jié)果成功時(shí)執(zhí)行,一個(gè)是Promise獲取結(jié)果失敗時(shí)執(zhí)行。如果promises獲取結(jié)果成功那么獲取到的結(jié)果將作為成功時(shí)執(zhí)行的回調(diào)方法的參數(shù)。相反的,如果Promise獲取結(jié)果失敗,那么最終結(jié)果(失敗的原因)將作為Promise失敗時(shí)執(zhí)行的回調(diào)方法的參數(shù)。比如下面的代碼段,當(dāng)Promise獲取結(jié)果成功時(shí)執(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é)果成功時(shí)執(zhí)行
    fulfill: function(answer) {
      console.log("The answer is " + answer);
    },


    //  promises獲取結(jié)果失敗時(shí)執(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的一個(gè)簡單例子,promises的then方法會(huì)根據(jù)promises的獲取到的最終結(jié)果執(zhí)行不同的回調(diào),如果promises獲取結(jié)果成功則執(zhí)行fulfill回調(diào),否則執(zhí)行reject回調(diào)。

promises的強(qiáng)大之處不僅僅如此,promises還可以以鏈的形式執(zhí)行多個(gè)then方法,每個(gè)then方法都會(huì)根據(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');
    }
});

這種情況下會(huì)打印什么結(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)方法。瀏覽器控制臺(tái)打印結(jié)果如下:

fetchPhotoOfUsers
applyInstagramFilters
uploadThrendyPhotAlbum
displaySuccessMessage

但是如果我把Ember.$.getJSON('https://api.github.com/repos/emberjs/ember.js/pulls');改成一個(gè)不存在的URL,比如改成Ember.$.getJSON('https://www.my-example.com');執(zhí)行代碼之后控制臺(tái)會(huì)提示出404錯(cuò)誤,并且打印'handleErrors'。說明promises獲取結(jié)果失敗,執(zhí)行了then里的reject回調(diào)。為了驗(yàn)證每個(gè)回調(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)用方式有點(diǎn)類似于try……catch……,但是本文的重點(diǎn)不是講解promises,更多有關(guān)promises的教材請讀者自行Google或者百度吧,在這里介紹一個(gè)js庫RSVP.js,它可以讓你更加簡單的組織你的promises代碼。 在附上幾個(gè)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/

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

2,promises中止路由

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

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

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

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

//  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)入路由tardy,model回調(diào)就會(huì)被執(zhí)行并且返回一個(gè)延遲3秒才執(zhí)行的promises,在這期間路由會(huì)中止。當(dāng)promises返回fulfill路由會(huì)繼續(xù)執(zhí)行,并將model返回的對象傳遞到setupController方法中。

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

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

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

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

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


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

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

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

result

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

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

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

//  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):其實(shí)就是在reject回調(diào)中繼續(xù)執(zhí)行fulfill狀態(tài)下的代碼。' };
        });
    }
});

為了驗(yàn)證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)容也全部介紹完畢了??!難點(diǎn)在Ember.js 入門指南之二十六查詢參數(shù)這一篇。能力有限沒有把這篇的內(nèi)容講明白,暫時(shí)擱下待日后完善!

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

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


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

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

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)