移動web app開發必備 - 異步隊列 Deferred

背景

移動web app開發,異步代碼是時常的事,好比有常見的異步操做:javascript

  • Ajax(XMLHttpRequest)
  • Image Tag,Script Tag,iframe(原理相似)
  • setTimeout/setInterval
  • CSS3 Transition/Animation
  • HTML5 Web Database
  • postMessage
  • Web Workers
  • Web Sockets
  • and more…

後面幾個是CSS3 HML5加入的新API.這些接口都是會產生異步的操做前端

好比本人的一個phonegap項目,操做HTML5本地數據庫(HTML5 Web Database)就是一個異步的過程,若是同時執行多個查詢,勢必同步代碼要等待數據查詢結束後調用java

附項目源碼:執行屢次異步查詢

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
/**
   * 初始化操做
   * @return
   */
  proto.initProcess =  function (){
      var  self =  this ,
          prev =  null  ,
          curr =  null  ,
          next =  null  ;
      debug.group( "start of init process" );
      var  idx = self.chapterIndex;
      debug.info( "PageBase: 執行初始化以前的操做!" );
      self.initProcessBefore();
 
      if (idx == 0){
          debug.info( "PageBase: 初始化入口點從第一章開始進入" );
          debug.info( "PageBase: 解析器解析第一章數據!" );
          curr = self.process(self.chapters[idx]);
          curr.then( function (pages){
              debug.info(self.format( "PageBase: 第一章數據解析完成,解析頁面數爲{0}"  , pages.length));
              self.cPages = pages;
              if (self.isChangeFont){
                self.idx = Math.ceil((pages.length - 1) * self.idx);                 
              }
 
              self.cPages.idx = idx;
 
              /////////////////////////////////////////////////
              //
              // 2013.1.10修改
              //   若是隻有一個章節的狀況下
              //
              if (1 === self.chapters.length){
                deferred.all([curr]).then(self.steup.bind(self));  
              } else {
                debug.info( "PageBase:解析器解析後一章數據!" );
                next = self.loadNextData(idx + 1);
                next.then( function (args){
                    debug.info(self.format( "PageBase: 後一章數據解析完成,解析頁面數爲{0}"  , args.pages.length));
                    self.nPages = args.pages;
                    self.nPages.idx = idx + args.index;
                    debug.info(self.format( "PageBase: 初始化數據解析完成, 當章索引{0} 當章頁數{1} 下章索引{2}  下章頁數{3}"
                            , self.cPages.idx , self.cPages.length , self.nPages.idx , self.nPages.length));
              
                    debug.info( "PageBase: 初始化數據解析完成,即將生成結構操做!" );
                });
                deferred.all([curr , next]).then(self.steup.bind(self));  
              }
 
          });
      } else  if (idx == self.chapters.length -1){
          debug.info( "PageBase: 初始化入口點從最後一章開始進入" );
          debug.info( "PageBase:解析器解析最後一章數據!" );
          prev = self.loadPrevData(idx - 1);
          prev.then( function (args){
              self.pPages = args.pages;
              self.pPages.idx = args.index + 1;
              debug.info(self.format( "PageBase: 最後一章的前一章數據解析完成,解析頁面數爲{0}"  , args.pages.length));
              curr = self.process(self.chapters[idx]);
              curr.then( function (pages , data){
                  if (self.isChangeFont){
                    self.idx = Math.ceil((pages.length - 1) * self.idx);                 
                  }
                  self.cPages = pages ;
                  self.cPages.idx = idx;
                  debug.info(self.format( "PageBase: 最後一章數據解析完成,解析頁面數爲{0}"  , pages.length));
                  debug.info(self.format( "PageBase: 初始化數據解析完成, 前章索引{0} 前章頁數{1} 當章索引{2} 當章頁數{3} "
                          , self.pPages.idx , self.pPages.length , self.cPages.idx , self.cPages.length ));
            
                  debug.info( "PageBase: 初始化數據解析完成,即將生成結構操做!" );
              });
              deferred.all([prev , curr]).then(self.steup.bind(self));
          });
      } else {
          debug.info( "PageBase: 初始化入口點從中間章開始進入" );
          prev = self.loadPrevData(idx - 1);
          debug.info( "PageBase:解析器解析中間章的前一章數據!" );
          prev.then( function (args){
              self.pPages = args.pages ;
              self.pPages.idx = args.index;
              debug.info(self.format( "PageBase: 中間章前一章數據解析完成,解析頁面數爲{0}"  , args.pages.length));
              debug.info( "PageBase:解析器解析中間章數據!" );
              curr = self.process(self.chapters[idx]);
              curr.then( function (pages , data){
                  if (self.isChangeFont){
                      self.idx = Math.ceil((pages.length) * self.idx);
                      // console.log("spages.length - 1",pages.length)     
                      // console.log("self.idx",self.idx)            
                  }
                  self.cPages = pages ;
                  self.cPages.idx = idx;
                  debug.info(self.format( "PageBase: 中間章數據解析完成,解析頁面數爲{0}"  ,pages.length));
                  debug.info( "PageBase:解析器解析中間章的後一章數據!" );
                  next = self.loadNextData(idx + 1);
                  next.then( function (args){
                      self.nPages = args.pages ;
                      self.nPages.idx = idx + args.index;
                      debug.info(self.format( "PageBase: 中間章後一章數據解析完成,解析頁面數爲{0}"  , args.pages.length));
                      debug.info(self.format( "PageBase: 初始化數據解析完成, 前章索引{0} 前章頁數{1} 當章索引{2} 當章頁數{3} 下章索引{4}  下章頁數{5}"
                          , self.pPages.idx , self.pPages.length , self.cPages.idx , self.cPages.length , self.nPages.idx , self.nPages.length));
                      debug.info( "PageBase: 初始化數據解析完成,即將生成結構操做!" )
                  });
                  deferred.all([prev , curr , next]).then(self.steup.bind(self)); 
              });
          });
     }

 

如何組織代碼

可是對於異步+回調的模式,當須要對一系列異步操做進行流程控制的時候彷佛必然會面臨着回調嵌套。所以怎麼把異步操做「拉平」,用更好的方法去優化異步編程的體驗,同時也寫出更健壯的異步代碼,是這兩年來前端圈子裏很火的話題。jquery

表明的git

  1. 消息驅動——表明:@樸靈 的EventProxy
  2. Promise模式——表明:CommonJS PromisesjQueryDojo 
  3. 二次編譯——表明:@老趙 的Jscex
  4. jQuery 是惟一的實現了這種 when 方法的庫。其餘的 promises 庫,例如  QDojo, 和  when 依照  Promises/B spec 實現了 when 方法, 可是並無實現註釋者說起的 when 方法。可是,Q 庫有一個   all方法,when.js 也有一個  parallel方法,與上面的 jQuery.when 方法做用同樣,只是它們接受一個數組類型的參數,而不是任意數量的參數。

 回顧Jquery Deferred

  • 從1.5版本開始,jQuery加入了Deferred功能,讓事件處理隊列更加的完善。並用 這個機制重寫了Ajax模塊。雖然還沒輪到Ajax,可是接下來的事件處理函數中牽扯到了 這個機制
  • Deferred把回調函數註冊到一個隊列中,統一管理,而且能夠同步或者異步地調用 這些函數。jQuery.Deferred()用來構造一個Deferred對象。該對象有狀態值,共有三種: Rejected, Resolved和初始狀態。其中Resolved表示該操做成功完成了,而Rejected 則表示出現了錯誤,調用失敗。Deferred對象的主要成員以下:
  • done(callback): 註冊一個callback函數,當狀態爲resolved時被調用。
  • fail(callback): 註冊一個callback函數,當狀態爲rejected時被調用。
  • always(callback): 註冊一個callback函數,不管是resolved或者rejected都會被 調用。
  • then(successCallback, failureCallback): 同時傳入成功和失敗的回調函數。
  • pipe(successFilter, failureFilter): 在調用成功和失敗的回調函數前先調用pipe 指定的函數。算是一種管道機制,攔截了函數調用。
  • resolve(args): 把狀態設置爲Resolved。
  • reject(args): 把狀態設置爲Rejected。
  • promse(): 返回的是一個不完整的Deferred的接口,沒有resolve和reject。即不能 修改Deferred對象的狀態。能夠看做是一種只讀視圖。這是爲了避免讓外部函數提前觸發 回調函數。好比$.ajax在1.5版本後再也不返回XMLHttpRequest,而是返回一個封裝了 XMLHttpRequest和Deferred對象接口的object。其中Deferred部分就是promise()獲得 的,這樣不讓外部函數調用resolve和reject,防止在ajax完成前觸發回調函數。把這 兩個函數的調用權限保留給ajax內部。

 deferred-js

本人在項目中使用 Promise/A 規範實現的 deferred-js , 比較簡單輕巧.github

如何使用?web

API:ajax

var  DeferredAPI = {
     deferred     : deferred,
     all          : all,
     Deferred     : Deferred,
     DeferredList : DeferredList,
     wrapResult   : wrapResult,
     wrapFailure  : wrapFailure,
     Failure      : Failure
}

最簡單經常使用的案例

複製代碼
           //Deferred對象建立
           var d = new deferred.Deferred()

           //添加一個回調到遞延的回調鏈
           d.then(function(result) {
               console.log('Hello ' + result)
               return result
           })

           //等待回調後觸發
           d.resolve('World')
複製代碼

每一個連接在一個回調鏈能夠是兩個函數,表明一個成功,一個失敗數據庫

只有一個成功回調

d.then(function(result) {
    // 本身的代碼
    return result
})

 

失敗回調

d.fail(function(failure) {
    // optionally do something useful with failure.value()
    return failure
});

 

添加一個成功方法和一個失敗方法

複製代碼
d.then(function(result) {
    // do something useful with the result
    return result
}, function(failure) {
    // optionally do something useful with failure.value()
    return failure
})
複製代碼

無論回調成功或者失敗都執行同一份代碼

d.both(function(result) {
    // in the case of failure, result is a Failure
    // do something in either case
    return result
})

 

若是許多異步在操做,好比提供的案例,在要執行HTML5數據庫N次後,如何操做呢?

 請仔細對照下案例中的編程

 deferred.all([prev , curr , next]).then(self.steup.bind(self));  

all的方法等待全部的延時隊列加載完畢後,才執行後續代碼

 

使用起來很方便,很精簡沒有那麼多複雜的概念

使用教程以後,下一節附源碼的實現

相關文章
相關標籤/搜索