參考鏈接:
學習RxJS: 導入
http://www.moye.me/2016/05/31/learning_rxjs_part_one_preliminary/
RxJS中文文檔
https://cn.rx.js.org
Lodash中文文檔
https://www.lodashjs.com
阮一峰的ES6入門:Promise 對象
http://es6.ruanyifeng.com/#docs/promise
ES6關于Promise的用法
https://segmentfault.com/a/1190000011652907
ES6 Promise 用法(我見過最簡潔優秀的文章)
https://blog.csdn.net/shan1991fei/article/details/78966297
引子
新手們在異步編程里跌倒時,永遠會有這么一個經典問題:
怎么在一次異步調用里return一個結果啊?
老司機說要用【回調函數】,然后有條件判斷的嵌套回調(回調地獄)問題來了;
老司機推薦用【事件】,然后異步流程里有順序依賴;
老司機推薦用【Promise】,然后有順序依賴的流程里,居然還想訂閱事件;
老司機建議試試【協程】,誰知對方想要合并兩個異步調用;
……
以上,是異步編程里要面對的一些難題,也是【ReactiveX API】 所致力解決的
一、Promise的用法
- Promise 的含義
- 基本用法
- Promise.prototype.then()
- Promise.prototype.catch()
- Promise.prototype.finally()
- Promise.all()
- Promise.race()
- Promise.resolve()
- Promise.reject()
- 應用
- Promise.try()
Promise 的含義
Promise 是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。它由社區最早提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了Promise
對象。
所謂Promise
:
簡單說就是一個容器,里面保存著某個未來才會結束的事件(通常是一個異步操作)的結果。
從語法上說,Promise 是一個對象,從它可以獲取異步操作的消息。Promise 提供統一的 API,各種異步操作都可以用同樣的方法進行處理。
Promise
對象有以下兩個特點。
(1)對象的狀態不受外界影響。Promise
對象代表一個異步操作,有三種狀態:pending
(進行中)、fulfilled
(已成功)和rejected
(已失敗)。只有異步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。這也是Promise
這個名字的由來,它的英語意思就是“承諾”,表示其他手段無法改變。
(2)一旦狀態改變,就不會再變,任何時候都可以得到這個結果。
Promise
對象的狀態改變,只有兩種可能:
從pending
變為fulfilled
從pending
變為rejected
。
只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果,這時就稱為 resolved(已定型)。
如果改變已經發生了,你再對Promise
對象添加回調函數,也會立即得到這個結果。這與事件(Event)完全不同,事件的特點是,如果你錯過了它,再去監聽,是得不到結果的。
注意,為了行文方便,本章后面的resolved
統一只指fulfilled
狀態,不包含rejected
狀態。
有了Promise
對象,就可以將異步操作以同步操作的流程表達出來,避免了層層嵌套的回調函數。此外,Promise
對象提供統一的接口,使得控制異步操作更加容易。
Promise
也有一些缺點。
1、無法取消Promise
,一旦新建它就會立即執行,無法中途取消。
2、如果不設置回調函數,Promise
內部拋出的錯誤,不會反應到外部。
3、當處于pending
狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。
如果某些事件不斷地反復發生,一般來說,使用 Stream 模式是比部署Promise
更好的選擇。
如果我們有幾個異步操作,并且后一個操作需要前一個操作返回的數據才能執行,這樣按照Node的一般執行規律,要實現有序的異步操作,通常是一層加一層嵌套下去。
為了解決這個問題,ES6提出了Promise的實現。
含義
Promise 對象用于一個異步操作的最終完成(或失敗)及其結果值的表示。
簡單點說,它就是用于處理異步操作的,異步處理成功了就執行成功的操作,異步處理失敗了就捕獲錯誤或者停止后續操作。
例子
var promise1 = new Promise(function(resolve, reject) {
// 2秒后置為接收狀態
setTimeout(function() {
resolve('success');
}, 2000);
});
promise1.then(function(data) {
console.log(data); // success
}, function(err) {
console.log(err); // 不執行
}).then(function(data) {
// 上一步的then()方法沒有返回值
console.log('鏈式調用:' + data); // 鏈式調用:undefined
}).then(function(data) {
// ....
});
在這里我們主要關注promise1.then()方法調用后返回的Promise對象的狀態,是pending還是fulfilled,或者是rejected?
返回的這個Promise對象的狀態主要是根據promise1.then()方法返回的值,大致分為以下幾種情況:
如果then()方法中返回了一個參數值,那么返回的Promise將會變成接收狀態。
如果then()方法中拋出了一個異常,那么返回的Promise將會變成拒絕狀態。
如果then()方法調用resolve()方法,那么返回的Promise將會變成接收狀態。
如果then()方法調用reject()方法,那么返回的Promise將會變成拒絕狀態。
如果then()方法返回了一個未知狀態(pending)的Promise新實例,那么返回的新Promise就是未知狀態。
如果then()方法沒有明確指定的resolve(data)/reject(data)/return data時,那么返回的新Promise就是接收狀態,可以一層一層地往下傳遞。
轉換實例如下:
var promise2 = new Promise(function(resolve, reject) {
// 2秒后置為接收狀態
setTimeout(function() {
resolve('success');
}, 2000);
});
promise2
.then(function(data) {
// 上一個then()調用了resolve,置為fulfilled態
console.log('第一個then');
console.log(data);
return '2';
})
.then(function(data) {
// 此時這里的狀態也是fulfilled, 因為上一步返回了2
console.log('第二個then');
console.log(data); // 2
return new Promise(function(resolve, reject) {
reject('把狀態置為rejected error'); // 返回一個rejected的Promise實例
});
}, function(err) {
// error
})
.then(function(data) {
/* 這里不運行 */
console.log('第三個then');
console.log(data);
// ....
}, function(err) {
// error回調
// 此時這里的狀態也是fulfilled, 因為上一步使用了reject()來返回值
console.log('出錯:' + err); // 出錯:把狀態置為rejected error
})
.then(function(data) {
// 沒有明確指定返回值,默認返回fulfilled
console.log('這里是fulfilled態');
});
下面代碼中,booksPromise和userPromise是兩個異步操作,只有等到它們的結果都返回了,才會觸發pickTopRecommentations這個回調函數。
const databasePromise = connectDatabase();
const booksPromise = databasePromise
.then(findAllBooks);
const userPromise = databasePromise
.then(getCurrentUser);
Promise.all([
booksPromise,
userPromise
])
.then(([books, user]) => pickTopRecommentations(books, user));
下面代碼中,如果 5 秒之內fetch方法無法返回結果,變量p的狀態就會變為rejected,從而觸發catch方法指定的回調函數。
const p = Promise.race([
fetch('/resource-that-may-take-a-while'),
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), 5000)
})
]);
p
.then(console.log)
.catch(console.error);