1. 程式人生 > >淺談Promise物件在ReactNative中的使用

淺談Promise物件在ReactNative中的使用

寫在前面

假設現在一個日常開發會遇到這樣一個需求:多個介面非同步請求,第二個介面依賴於第一個

介面執行完畢之後才能利用資料進行一系列操作。一般會這樣寫:

            A.fetchData({
                url: 'http://......',
                success: function (data) {
                    A.fetchData({
                        // 要在第一個請求成功後才可以執行下一步
                        url: 'http://......',
                        success: function (data) {
                             // ......
                        }
                    });
                }
            });

這樣寫沒問題,但是有兩個缺點:


1、當有多個操作的時候,會導致多個回撥函式巢狀,不夠美觀


2、如果有幾個操作沒有前後順序之分時,例如上面的後一個請求不依賴與前一個請求的返回結果的時候,

同樣也需要等待上一個操作完成再實行下一個操作。

從ES6開始,Promise物件可以解決上述問題。

什麼是Promise物件

一個Promise物件可以理解為一次將要執行的操作,使用了Promise物件之後可以用一種鏈式呼叫的方式

來組織程式碼,讓程式碼更加直觀。

resolve和reject

先看程式碼:

  function helloWorld (ready) {
    return new Promise(function (resolve, reject) {
        if (ready) {
            resolve("Hello World!");
        } else {
            reject("Good bye!");
        }
    });
  }

    helloWorld(true).then(function (message) {
        alert(message);
    }, function (error) {
        alert(error);
    });

上面的程式碼實現的功能非常簡單,helloWord 函式接受一個引數,如果為 true 就列印 "Hello World!",

如果為 false 就列印錯誤的資訊。helloWord 函式返回的是一個 Promise 物件。

在 Promise 物件當中有兩個重要方法————resolve 和 reject。

resolve 方法可以使 Promise 物件的狀態改變成成功,同時傳遞一個引數用於後續成功後的操作,

在這個例子當中就是 Hello World! 字串。

reject 方法則是將 Promise 物件的狀態改變為失敗,同時將錯誤的資訊傳遞到後續錯誤處理的操作。

then

Promise 物件有三種狀態:
1.Fulfilled 可以理解為成功的狀態
2.Rejected 可以理解為失敗的狀態
3.Pending 既不是 Fulfilld 也不是 Rejected 的狀態,可以理解為 Promise 物件例項建立時候的初始狀態。

helloWorld 的例子中的 then方法就是根據 Promise 物件的狀態來確定執行的操作,resolve 時執行第一個

函式(onFulfilled),reject 時執行第二個函式(onRejected)。promise模式在任何時刻都處於以下三種狀態之一:未完成(unfulfilled)、已完成(resolved)和拒絕(rejected)。以CommonJS Promise/A 標準為例,promise物件上的then方法負責新增針對已完成和拒絕狀態下的處理函式。then方法會返回另一個promise物件,以便於形成promise管道,這種返回promise物件的方式能夠支援開發人員把非同步操作串聯起來,如then(resolvedHandler, rejectedHandler); 。resolvedHandler 回撥函式在promise物件進入完成狀態時會觸發,並傳遞結果;rejectedHandler函式會在拒絕狀態下呼叫。

示例程式碼1:

function printHello (ready) {
  var promise = new Promise(function (resolve, reject) {
      if (ready) {
          resolve("Hello");
      } else {
          reject("Good bye");
      }
  });
  return promise;
}

function printWorld () {
    console.log('World');
}

function printExclamation () {
    console.log('!!!');
}

 printHello(true)
 .then(function(message)
 .then(printWorld)
 .then(printExclamation)
 .catch(function(error){
    console.log(error);
  });;

函式先執行printHello,返回一個promise物件,通過then將非同步操作串聯起來。
執行結果應該是 Hello
World
!!!

示例程式碼2:

  function helloWorld (ready) {
    return new Promise(function (resolve, reject) {
        if (ready) {
            resolve("Hello World!");
        } else {
            reject("Good bye!");
        }
    });
  }

  var _this = this;
  printHello(true)
  .then(function (message) {
  var REQUEST_URL = 'https://raw.githubusercontent.com/facebook/react-native/master/docs/MoviesExample.json';
   return  fetch(REQUEST_URL)
      .then((response) => response.json())
      .then((responseData) => {
        var movie = responseData.movies[1];
        console.log('data   =   ', movie.title);
        return movie.title;
      })
  },function (error) {
      return(error);
  }).then(function (message) {
      return message  + ' World';
  }).then(function (message) {
      return message + '!!!';
  }).then(function (message) {
     console.log(message);
     console.log('finally');
  }).catch(function(error){
     console.log(error);
  });

上面的程式碼中有兩個promise,第一個promise執行完畢後也就是printHello之後,會執行下一個then,

這個then返回了一個獲取資料的promise,後面的then拿到的promise都是指向這個promise物件的。上述例子通過鏈式呼叫的方式,按順序打印出了相應的內容。then 可以使用鏈式呼叫的寫法原因在於,每一次執行該方法時總是會返回一個 Promise 物件。另外,在 then onFulfilled 的函式當中的返回值,可以作為後續操作的引數。

catch

catch 方法是 then(onFulfilled, onRejected) 方法當中 onRejected 函式的一個簡單的寫法,也就是說

可以寫成 then(fn).catch(fn),相當於 then(fn).then(null, fn)。使用 catch 的寫法比一般的寫法更加清晰明確。

新手使用容易犯的錯誤

1.忘記新增catch()方法

這是一個很常見的錯誤。很多程式設計師對他們程式碼中的promise呼叫十分自信,覺得程式碼永遠不會丟擲一個 error ,

也可能他們只是簡單的忘了加 catch() 方法。不幸的是,不加 catch() 方法會讓回撥函式中丟擲的異常被吞噬,

在你的控制檯是看不到相應的錯誤的,這對除錯來說是非常痛苦的。

為了避免這種糟糕的情況,我已經養成了在自己的promise呼叫鏈最後新增如下程式碼的習慣:

      somePromise().then(function () {
        return aPromise();
      }).then(function () {
        return anotherPromise();
      }).catch(function(error){
               console.log(error);
            });

即使你並不打算在程式碼中處理異常,在程式碼中新增 catch() 也是一個謹慎的程式設計風格的體現。在某種情況下

你原先的假設出錯的時候,這會讓你的除錯工作輕鬆一些。

2.return的混淆亂用

在then方法內部,我們可以做三件事:
1.return 一個promise物件
2.return一個同步的值或者是 undefined
3.同步的 throw 一個錯誤

理解這三種情況之後,你就會理解promise了。

1.返回另一個promise物件

在有關promise的相關文章中,這種寫法很常見,就像上文提到的構成promise鏈的一段程式碼:

        getUserByName('nolan').then(function (user) {

          return fetch(REQUEST_URL)
                .then((response) => response.json())
                .then((responseData) => {

                 }        

        }).then(function () {
        });

2.返回一個具體的值或者是 undefined

    getUserByName('nolan').then(fcuntion (user) {
        if (inMemoryCache[user.id]) {
            return inMemoryCache[user.id];  
            // returning a value!
        }
        return inMemoryCache[user.id];
         // returning a promise
    }).then(function (userAccount) {
        // I got a user account
    })

如果不呼叫return語句的話,javaScript裡的函式會返回 undefined 。這也就意味著在你想返回一些值的時候,

不顯式呼叫return會產生一些副作用。

出於上述原因,養成了一個個人習慣就是在then方法內部永遠顯式的呼叫return或者throw。我也推薦你這樣做。

3.丟擲一個錯誤

說到throw,這又體現了promise的功能強大。在使用者退出的情況下,我們的程式碼中會採用丟擲異常的方式進行處理:

    getUserByName('nolan').then(function (user) {
      if (user.isLoggedOut()) {
        throw new Error('user logged out!'); 
        // throwing a error!
      }
      if (inMemoryCache[user.id]) {
        return inMemoryCache[user.id];      
         // returning a value!
      }
      return getUserAccountById(user.id);   
       // returning a promise!
    }).then(function (userAccount) {
      // I got a user account!
    }).catch(function (err) {
      // Boo, I got an error!
    });

如果使用者已經登出的話, catch() 會收到一個錯誤,如果有promise物件的狀態變為rejected的話,

它還會收到一個錯誤。


在使用promise的時候丟擲異常在開發階段很有用,它能幫助我們定位程式碼中的錯誤。比方說,

在then函式內部呼叫 JSON.parse() ,如果JSON物件不合法的話,可能會丟擲異常,在回撥函式中,這個異常會被吞噬,但是在使用promise之後,我們就可以捕獲到這個異常了。