1. 程式人生 > >Angular非同步變同步處理

Angular非同步變同步處理

1. 背景

在前端專案中,經常會遇到頁面有多個介面,後一個介面引數需要從前一個介面的返回資料中獲取,這就存在介面必須按順序一個一個執行。而Angular提供的Http服務請求介面都是非同步請求,因此通常情況下會寫成如下的程式碼:

funA(arg1,arg2,function(){
    funcB(arg1,arg2,function(){
        funcC(arg1,arg2,function(){
             xxxx....
        })
    })   
});

這不僅造成頁面程式碼混亂不易維護,也讓開發者無法追蹤程式碼的執行。本身巢狀就已經不容易理解,加上不知何時才能觸發回撥,這就相當於雪上加霜。

2. Promise

針對這中現象,Angular推出了Promise規範,它可以幫助開發者將非同步變成同步,是一種對執行結果不確定的一種預先定義。例如以下程式碼中,當getData方法內的物件成功執行,就會呼叫success方法,如果失敗,就會呼叫error方法。另外promise除了then方法之外,還有catch()finally()方法。

defer. getData()
.then( funcSuccess (){},  funcError (){} )
.catch( function(){})
.finally( function(){});

Finally()方法一般用來處理不管promise

是返回成功還是失敗,都會執行的操作。例如ionic中的上拉載入,當獲取資料後不管成功或者失敗,都要廣播結束這次上拉操作,便可將這個操作寫在finally()方法中。

3. $q服務

q服務是AngularJs中自己封裝實現的一種Promise實現,一般有如下三個常用方法:

  • defer() :建立一個deferred物件,這個物件可以執行幾個常用的方法,比如resolverejectnotify等;
  • all() :傳入Promise的陣列,批量執行,返回一個Promise物件;
  • when() :傳入一個不確定的引數,如果符合Promise標準,就返回一個Promise物件。

3.1. defer() 方法

$q服務中,用defer()方法建立一個deferred物件,然後這個物件可以呼叫resolve方法定義成功狀態,使用reject方法定義失敗狀態,並且可以在這些方法中傳遞引數,一般介面返回的資料都是用這些方法傳遞出去最後通過deferred物件 .promise來返回一個promise物件,來定義then方法。then方法中有三個引數,分別是成功回撥、失敗回撥、狀態變更回撥。

定義方法:

function funA(num){
  var defer = $q.defer();
  if(num<5){
    defer.resolve('funA success');
  }else{
    defer.reject('funA error');
  }
 return defer.promise;
}

方法呼叫(返回成功)

funA(3).then(function(success){
  console.log(success);
},function(err){
  console.log(err);
})

結果:

 

方法呼叫(返回失敗)

funA(8).then(function(success){
  console.log(success);
},function(err){
  console.log(err);
})

結果:

 

3.2. 鏈式呼叫

瞭解了defer()方法與then()方法,接下來就能解決多個介面巢狀呼叫的問題了。

一般處理多個介面需要巢狀執行時,可採用angular的鏈式呼叫,即:

FunA.then().then().then();

then方法中return 要呼叫的下一個介面。如下funAfunBfunC都是返回了promise物件的方法,如果執行順序為funAfunBfunC,則可用鏈式呼叫。

function funA(num){
  var defer = $q.defer();
  if(num<5){
    defer.resolve('funA success');
  }else{
    defer.reject('funA error');
  }
  return defer.promise;
}

function funB(num){
  var defer = $q.defer();
  if(num<5){
    defer.resolve('funB success');
  }else{
    defer.reject('funB error');
  }
  return defer.promise;
}

function funC(num){
  var defer = $q.defer();
  if(num<5){
    defer.resolve('funC success');
  }else{
    defer.reject('funC error');
  }
  return defer.promise;
}

方法呼叫:

funA(3).then(function(success){
  console.log(success);
  return funB(3);
}).then(function(success){
  console.log(success);
  return funC(3);
}).then(function(success){
  console.log(success);
},function(err){
  console.log(err);
});

結果:

 

上述方法呼叫中,then()方法中只寫了成功引數的方法,如果其中一個方法呼叫返回失敗,則該方法後邊的方法都不會再執行,也就是隻有前一個方法呼叫成功,才能呼叫後邊的方法。如下為其中一個方法失敗的示例,funB方法呼叫返回失敗,則不會再執行funC,而錯誤的資訊會在最後捕捉錯誤資訊的引數方法中獲取。

方法呼叫:

funA(3).then(function(success){
  console.log(success);
  return funB(8);
}).then(function(success){
  console.log(success);
  return funC(3);
}).then(function(success){
  console.log(success);
},function(err){
  console.log(err); //列印funB()的錯誤資訊
});

結果:

 

如果在某些場景中,等前一個方法呼叫完畢,而不管這個方法是否呼叫成功,都要繼續呼叫後邊的方法,則需要在then()方法中增加錯誤回撥並return 下一個執行的promise。如下:

funA(3).then(function(success){
  console.log(success);
  return funB(8);
},function(err){
  console.log(err);
  return funB(8);
}).then(function(success){
  console.log(success);
  return funC(3);
},function(err){
  console.log(err);
  return funC(3);
}).then(function(success){
  console.log(success);
},function(err){
  console.log(err);
});

結果:

 

3.3. all() 方法

all()方法可以把多個promise的數組合併成一個。當所有的promise執行成功後,會執行後面的回撥,回撥中的引數,是每個promise執行的結果。

function funA(num){
  var defer = $q.defer();
  if(num<5){
    defer.resolve('funA success');
  }else{
    defer.reject('funA error');
  }
  return defer.promise;
}

function funB(num){
  var defer = $q.defer();
  if(num<5){
    defer.resolve('funB success');
  }else{
    defer.reject('funB error');
  }
  return defer.promise;
}

方法呼叫:

$q.all([
    funA(3), 
    funB(3)
  ])
.then(function(success){
  console.log(success);
},function(err){
  console.log(err);
})

結果:

 

all() 方法中的引數可以是上面的陣列形式,也可以是json格式。相應的,如果引數是陣列格式,那麼返回的資料格式也是陣列格式;如果引數是json格式,那麼返回的資料格式也是json格式。json引數示例如下:

$q.all(
  {
    funA:DeferService.funA(3),
    funB:DeferService.funB(3)
  })
.then(function(success){
  console.log(success);
},function(err){
  console.log(err);
})

結果:

 

上面兩個示例展示的都是獲取資料成功的情況。而當all()方法的引數中存在一個promise失敗,則整個任務都失敗,返回的資料也只會返回該失敗引數的失敗資訊。

方法呼叫:

$q.all(
  {
    funA:DeferService.funA(8), //呼叫失敗
    //正常情況下是呼叫成功,但由於funA呼叫失敗,固該方法也是失敗
    funB:DeferService.funB(3)  
})
.then(function(success){
  console.log(success);
},function(err){
  console.log(err);
})

結果:

 

3.4. when() 方法

when()方法中的引數可以是一個值,也可以是一個promise,這個方法是把傳入的引數處理包裝成一個promise。這個方法一般在你不確定所處理的物件或者呼叫的方法是不是一個promise時使用。

例如在下面的例子中,val=10,並不是一個promise,但是經過when()方法包裝之後,可以通過.then()方法去呼叫,像處理平常的promise一樣。

var val=10;
$q.when(val)
  .then(function(success){
    console.log(success);
  },function(err){
    console.log(err);
  });

結果: