淺談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之後,我們就可以捕獲到這個異常了。