文章目錄
Promise 對象
1.0 Promise 的含義
Promise 是一步編程的一種解決方案, 比傳統的解決方案 — 回調函數和事件 — 更合理和更加強大.
所謂
Promise
, 簡單來說就是一個容器, 裏面保存着某個未來纔會結束的事件(通常是一個一步操作)的結果.
Promise
對象有以下兩個特點.
- 對象的狀態不受外界影響.
Promise
對象代表一個一步操作, 有三種狀態:pending
,fulfilled
和rejected
. 只有異步操作的結果, 可以決定當前是哪一種狀態, 其他任何操作都無法改變這個狀態.- 一旦狀態改變, 就不會再變, 任何時候都可以得到這個結果.
Promise
對象的狀態改變, 只有兩種可能: 從pending
變爲fulfilled
和從pending
變爲rejected
. 這要這兩種情況發生, 狀態就凝固了, 不會再改變了, 會一直保持這個結果, 這時就稱爲 resolved(已定性). 如果改變已經發生了, 你再對promise
對象添加回調函數, 也會立即得到這個結果. 這與事件完全不同, 事件的特點就是, 如果你錯過了它, 你再去監聽,是得不到結果的.
有了
Promise
對象, 就可以將異步的操作 以 同步的流程表達出來., 避免了層層嵌套函數. 此外,Promise
對象提供統一的接口,使得控制異步操作更加容易.
Promise
也有一些缺點,:
- 首先, 無法取消
Promise
, 一旦新建它就會立即執行, 無法取消.- 其次, 如果不設置回調函數,
Promise
內部拋出的錯誤, 不會反映到外部.- 第三, 當處於
pending
狀態時, 無法得知目前進展到哪一個階段(即: 無法得知是剛剛開始還是即將完成).
2.0 基本用法
ES6 規定,
promise
對象是一個構造函數, 用來生成Promise
實例.下面代碼創造了一個
Promise
實例const promise = new Promise(function(resolve, reject) { // ... some code if (/* 異步操作成功 */){ resolve(value); } else { reject(error); } });
Promise
構造函數接收一個函數作爲參數, 該函數的兩個參數分別是resolve
和reject
. 它們是兩個函數, 有JavaScript 引擎提供, 不用自己部署.
resolve
函數的作用是, 將Promise
對象的狀態從"未完成" 變爲 “成功”(即從 pending 變爲 resolved), 在異步操作成功時調用, 並將異步操作的結果, 作爲參數傳遞出去.
reject
函數的作用是, 將Promise
對象的狀態從 “未完成” 變爲 “失敗” (即從 pending 變爲 rejected), 在異步操作失敗時調用, 並將異步操作爆出的錯誤, 作爲參數傳遞出去.
Promise
實例生成以後, 可以用then
方法分別指定resolved
狀態和rejected
狀態的回調函數.promise.then((value)=>{ // success },(error)=>{ // failure })
then
方法可以接收兩個回調函數作爲參數,:
- 第一個回調函數是
Promise
對象的狀態變爲resolved
時調用- 第二個回調函數是
Promise
對象的狀態變爲rejected
時調用.其中, 第二個函數是可選的, 不一定要提供. 這兩個函數都接受
Promise
對象傳出的值作爲參數.下面是一個
Promise
對象的簡單例子.function timeout(ms) { return new Promise((resolve, reject) => { setTimeout(resolve, ms, 'done'); // 把 'done' 結果傳給 resolve 函數. }); } timeout(100).then((value) => { console.log(value); // done });
🏴 注意: setTimeout函數理解
var timeoutID = scope.setTimeout(function[, delay, arg1, arg2, ...]); var timeoutID = scope.setTimeout(function[, delay]); var timeoutID = scope.setTimeout(code[, delay]); // 參數理解 function: 是你想要在到期時間之後執行的函數 delay: 可選, 延遲的時間 arg1, ... , argN: 可選, 附件參數, 一旦定時器到期, 它們會作爲參數傳遞給 `function` # 代碼演示如下 ---------------------- setTimeout(function (value1, value2, value3) { console.log(value1,value2, value3); // 參數一 參數2 參數三 }, 1000, '參數一', '參數2','參數三')
上面代碼中,
timeout
方法返回一個Promise
實例, 表示一段時間以後纔會發生的結果. 過了指定的時間 (ms
參數)以後,Promise
實例的狀態變爲resolved
, 就會觸發then
方法綁定的回調函數.
Promise 新建後會立即執行.
let promise = new Promise(function(resolve, reject) { console.log('Promise'); resolve(); }); promise.then(function() { console.log('resolved.'); }); console.log('Hi!'); // Promise // Hi! // resolved
上面代碼中,Promise 新建後立即執行,所以首先輸出的是
Promise
。然後,then
方法指定的回調函數,將在當前腳本所有同步任務執行完纔會執行,所以resolved
最後輸出
3.0 Promise.prototype.then()
Promise 實例具有
then
方法, 也就是說,then
方法是定義在原型對象Promise.prototype
上的. 它 的作用是爲 Promise實例添加狀態改變時的回調函數. 前面說過,then
方法 的 第一個參數是resolved
狀態對應的回調函數, 第二個參數(可選) 是rejected
狀態對應的回調函數.
then
方法返回的是一個新的Promise
實例.(注意, 不是原來那個Promise
實例), 因此可以採用鏈式寫法, 即then
方法後面再調用另一個then
方法.getJSON("/posts.json").then(function(json) { return json.post; }).then(function(post) { // ... });
上面的代碼使用
then
方法,依次指定了兩個回調函數。第一個回調函數完成以後,會將返回結果作爲參數,傳入第二個回調函數。
4.0 Promise.prototype.catch()
Promise.prototype.catch()
方法是 `.then(null,rejection) 或.then(undefined,rejection)的別名, 用於指定發生錯誤時的回調函數.getJSON('/posts.json').then(function(posts) { // ... }).catch(function(error) { // 處理 getJSON 和 前一個回調函數運行時發生的錯誤 console.log('發生錯誤!', error); });
上面代碼中,
getJSON()
方法返回一個 Promise 對象,如果該對象狀態變爲resolved
,則會調用then()
方法指定的回調函數;如果異步操作拋出錯誤,狀態就會變爲rejected
,就會調用catch()
方法指定的回調函數,處理這個錯誤。另外,then()
方法指定的回調函數,如果運行中拋出錯誤,也會被catch()
方法捕獲。
5.0 Promise.prototype.finally()
finally()
方法用於指定不管 Promise 對象最後狀態如何,都會執行的操作。該方法是 ES2018 引入標準的。promise .then(result => {···}) .catch(error => {···}) .finally(() => {···});
上面代碼中,不管
promise
最後的狀態,在執行完then
或catch
指定的回調函數以後,都會執行finally
方法指定的回調函數。
finally
方法的回調函數不接受任何參數,這意味着沒有辦法知道,前面的 Promise 狀態到底是fulfilled
還是rejected
。這表明,finally
方法裏面的操作,應該是與狀態無關的,不依賴於 Promise 的執行結果。
6.0 Promise.all()
Promise.all()
方法用於將多個 Promise 實例, 包裝成一個新的 Promise實例.const p = Promise.all([p1, p2, p3]);
上面代碼中,
Promise.all()
方法接受一個數組作爲參數,p1
、p2
、p3
都是 Promise 實例,如果不是,就會先調用下面講到的Promise.resolve
方法,將參數轉爲 Promise 實例,再進一步處理。另外,Promise.all()
方法的參數可以不是數組,但必須具有 Iterator 接口,且返回的每個成員都是 Promise 實例。
p
的狀態由p1
、p2
、p3
決定,分成兩種情況。(1)只有
p1
、p2
、p3
的狀態都變成fulfilled
,p
的狀態纔會變成fulfilled
,此時p1
、p2
、p3
的返回值組成一個數組,傳遞給p
的回調函數。(2)只要
p1
、p2
、p3
之中有一個被rejected
,p
的狀態就變成rejected
,此時第一個被reject
的實例的返回值,會傳遞給p
的回調函數。
7.0 Promise.race()
Promise.race()
方法同樣是將多個 Promise 實例,包裝成一個新的 Promise 實例。const p = Promise.race([p1, p2, p3]);
上面代碼中,只要
p1
、p2
、p3
之中有一個實例率先改變狀態,p
的狀態就跟着改變。那個率先改變的 Promise 實例的返回值,就傳遞給p
的回調函數。
8.0 Promise.resolve()
有時需要將現有的對象轉換爲 Promise 對象,
Promise.resolve()
方法就起到了作用const jsPromise = Promise.resolve($.ajax('/whatever.json'));
上面代碼將jQuery 生成的
deferred
對象, 轉爲一個新的 Promise` 對象.
Promise.resolve()
等價於下面的寫法Promise.resolve('foo') // 等價於 new Promise(resolve =>resolve('foo'))
參數是一個 Promise實例
如果參數是一個Promise實例, 那麼
Promise.resolve
將不做任何修改, 原封不懂返回這個實例.參數是一個
thenable
對象
thenable
對象指的是具有then
方法的對象, 比如下面這個對象let thenable = { then: function(resolve, reject) { resolve(42); } };
Promise.resolve
方法會將這個對象轉爲 Promise 對象, 然後就立即執行thenable
對象的then
方法let thenable = { then: function(resolve, reject) { resolve(42); } }; let p1 = Promise.resolve(thenable); p1.then(function(value) { console.log(value); // 42 });
上面代碼中,
thenable
對象的then
方法執行後,對象p1
的狀態就變爲resolved
,從而立即執行最後那個then
方法指定的回調函數,輸出 42。參數不是具有
then
方法的對象, 或根本就不是對象如果參數是一個原始值, 或者是一個不具有
then
方法的對象, 則Promise.resolve
方法返回一個新的Promise 對象, 狀態爲resolved
.const p = Promise.resolve('Hello'); p.then(function (s){ console.log(s) }); // Hello
上面代碼生成一個新的 Promise 對象的實例
p
。由於字符串Hello
不屬於異步操作(判斷方法是字符串對象不具有 then 方法),返回 Promise 實例的狀態從一生成就是resolved
,所以回調函數會立即執行。Promise.resolve
方法的參數,會同時傳給回調函數。
- 不帶有任何參數
Promise.resolve()
方法允許調用時不帶參數, 直接返回一個resolved
狀態的 Promise 對象.所以, 如果希望得到一個 Promise 對象, 比較方便的方法就是直接調用
Promise.resolve()
方法.const p = Promise.resolve(); p.then(function () { // ... });
上面代碼的變量
p
就是一個 Promise 對象。需要注意的是,立即
resolve()
的 Promise 對象,是在本輪“事件循環”(event loop)的結束時執行,而不是在下一輪“事件循環”的開始時。setTimeout(function () { console.log('three'); }, 0); Promise.resolve().then(function () { console.log('two'); }); console.log('one'); // one // two // three
上面代碼中,
setTimeout(fn, 0)
在下一輪“事件循環”開始時執行,Promise.resolve()
在本輪“事件循環”結束時執行,console.log('one')
則是立即執行,因此最先輸出。
9.0 Promise.rejected()
Promise.reject(reason)
方法也會返回一個新的 Promise 實例,該實例的狀態爲rejected
。const p = Promise.reject('出錯了'); // 等同於 const p = new Promise((resolve, reject) => reject('出錯了')) p.then(null, function (s) { console.log(s) }); // 出錯了
上面代碼生成一個 Promise 對象的實例
p
,狀態爲rejected
,回調函數會立即執行。
注意,
Promise.reject()
方法的參數,會原封不動地作爲reject
的理由,變成後續方法的參數。這一點與Promise.resolve
方法不一致。const thenable = { then(resolve, reject) { reject('出錯了'); } }; Promise.reject(thenable) .catch(e => { console.log(e === thenable) }) // true
上面代碼中,
Promise.reject
方法的參數是一個thenable
對象,執行以後,後面catch
方法的參數不是reject
拋出的“出錯了”這個字符串,而是thenable
對象。
總結:
交流學習添加微信(備註技術交流學習):
Gene199302
該博客爲學習阮一峯 ES6入門課所做的筆記記錄, 僅用來留作筆記記錄和學習理解