其實想寫 Promise 的使用已經很長時間了。一個是在實際編碼的過程當中常常用到,一個是確實有時候小夥伴們在使用時也會遇到一些問題。
Promise 也確實是 ES6 中 對於寫 JS 的方式,有着真正最大影響的 API 特性之一。
本文是實際使用使用過程當中的一個總結
看一下文件建立時間 2017-10-09,拖延症真是太可怕了。。。仍是得加強執行力啊!不忘初心,加油吧!
博客原址
Promise 是解決 JS 異步的一種方案,相比傳統的回調函數,Promise 能解決多個回調嚴重嵌套的問題。html
Promise 對象表明一個異步操做,有三種狀態: pending、fulfilled 或 rejected
,狀態的轉變只能是 pending -> fulfilled
或者 pending -> rejected
,且這個過程一旦發生就不可逆轉。前端
<!-- more -->node
我的認爲講解 Promise 實際上須要分紅兩個部分es6
- 對於 Promise 構造函數的使用說明。
- Promise 原型對象上的一些方法。
ES6 規定,Promise 對象是一個構造函數,用來生成 Promise 實例。json
Promise 構造函數接受一個函數做爲參數,該函數的兩個參數分別是 resolve 和 reject 。它們是兩個函數,由 JavaScript 引擎提供,不用本身部署。api
resolve 函數的做用是將 Promise 對象的狀態從「未完成」變爲「成功」(即從 pending 變爲 fulfilled ),在異步操做成功時調用,並將異步操做的結果,做爲參數傳遞出去;
reject 函數的做用是,將 Promise 對象的狀態從「未完成」變爲「失敗」(即從 pending 變爲 rejected ),在異步操做失敗時調用,並將異步操做報出的錯誤,做爲參數傳遞出去。promise
下面代碼創造了一個 Promise 實例。併發
function request() { return new Promise((resolve, reject) => { /* 異步操做成功 */ setTimeout(() => { resolve("success"); }, 1000); // 取消註釋這裏能夠體現,Promise 的狀態一旦變動就不會再變化的特性 // reject('error'); }); }
接收異步
request() .then(result => { console.info(result); }) .catch(error => { console.info(error); });
上述 new Promise()
以後,除去用 catch 去捕獲錯誤以外,也能夠用 then
方法指定 resolve
和 reject
的回調函數
也能達到捕獲錯誤的目的。函數
request().then( result => { console.info(result); }, error => { console.info(error); } );
原型上的方法
Promise.prototype.then()
p.then(onFulfilled, onRejected);
then 方法 是定義在 Promise.prototype
上的方法,如上面的例子同樣,有兩個參數,fulfilled
的回調函數和 rejected
的回調函數,第二個參數時可選的。
兩個關鍵點:
Promise
實例,因此對於調用者而言,拿到一個 Promise
對象,調用 then
後仍然返回一個 Promise
,而它的行爲與 then 中的回調函數的返回值有關。以下:好比說回調形式: 一個回調地獄的例子
a(a1 => { b(a1, b1 => { c(b1, c1 => { d(c1, d1 => { console.log(d1); }); }); }); });
這樣的橫向擴展能夠修改爲(a,b,c,d)均爲返回 Promise 的函數
a() .then(b) .then(c) .then(d) .then(d1 => { console.log(d1); }); //===== 可能上面的例子並不太好看 ===下面這樣更直觀 a() .then(a1 => b(a1)) .then(b1 => c(b1)) .then(c1 => d(c1)) .then(d1 => { console.log(d1); });
這樣的縱向結構,看上去清爽多了。
Promise.prototype.catch()
除了 then()
,在 Promise.prototype
原型鏈上的還有 catch()
方法,這個是拒絕的狀況的處理函數。
其實 它的行爲與調用 Promise.prototype.then(undefined, onRejected)
相同。 (事實上, calling obj.catch(onRejected)
內部 calls obj.then(undefined, onRejected))
.
// 1. request().then( result => { console.info(result); }, error => { console.info(error); } ); // 2. request() .then(result => { console.info(result); }) .catch(error => { console.info(error); });
如上這個例子:兩種方式在使用,與結果基本上是等價的,可是 仍然推薦第二種寫法,下面我會給出緣由:
Promise.prototype.then(undefined, onRejected)
,onRejected
方法沒法捕獲當前 Promise 拋出的錯誤,然後續的 .catch 能夠捕獲以前的錯誤。new Promise((resolve, reject) => { setTimeout(() => { resolve("reject"); }, 1000); }) .then( result => { console.log(result + "1"); throw Error(result + "1"); // 拋出一個錯誤 }, error => { console.log(error + ":1"); // 不會走到這裏 } ) .then( result => { console.log(result + "2"); return Promise.resolve(result + "2"); }, error => { console.log(error + ":2"); } ); // reject1, Error: reject1:2
若是使用 .catch
方法,代碼會簡化不少,這樣其實是延長了 Promise 鏈
new Promise((resolve, reject) => { setTimeout(() => { resolve("reject"); }, 1000); }) .then(result => { console.log(result + "1"); throw Error(result + "1"); // 拋出一個錯誤 }) .then(result => { console.log(result + "2"); return Promise.resolve(result + "2"); }) .catch(err => { console.log(err); }); // reject1, Error: reject1:2
Promise.prototype.finally()
暫未徹底成爲標準的一部分,處於:Stage 4
finally()
方法返回一個 Promise
,在執行 then()
和 catch()
後,都會執行finally
指定的回調函數。(回調函數中無參數,僅僅表明 Promise 的已經結束
等同於使用 .then
+ .catch
延長了原有的 Promise 鏈的效果,避免一樣的語句須要在 then()
和 catch()
中各寫一次的狀況。
Promise.all()
用來處理 Promise 的併發Promise.all
會將多個 Promise
實例封裝成一個新的 Promise
實例,新的 promise 的狀態取決於多個 Promise
實例的狀態,只有在全體 Promise
都爲 fulfilled
的狀況下,新的實例纔會變成 fulfilled
狀態。;若是參數中 Promise
有一個失敗(rejected
),此實例回調失敗(rejecte
),失敗緣由的是第一個失敗 Promise
的結果。
舉個例子:
Promise.all([ new Promise(resolve => { setTimeout(resolve, 1000, "p1"); }), new Promise(resolve => { setTimeout(resolve, 2000, "p2"); }), new Promise(resolve => { setTimeout(resolve, 3000, "p3"); }) ]) .then(result => { console.info("then", result); }) .catch(error => { console.info("catch", error); }); // [p1,p2,p3] Promise.all([ new Promise(resolve => { setTimeout(resolve, 1000, "p1"); }), new Promise(resolve => { setTimeout(resolve, 2000, "p2"); }), Promise.reject("p3 error") ]) .then(result => { console.info("then", result); }) .catch(error => { console.info("catch", error); }); // p3 error
獲取 cnode 社區的 精華貼的前十條內容
fetch("https://cnodejs.org/api/v1/topics?tab=good&limit=10") .then(res => res.json()) .then(res => { const fetchList = res.data.map(item => { return fetch(`https://cnodejs.org/api/v1/topic/${item.id}`) .then(res => res.json()) .then(res => res.data); }); Promise.all(fetchList).then(list => { console.log(list); }); });
Promise.race()
競態執行Promise.race
也會將多個 Promise
實例封裝成一個新的Promise
實例,只不過新的 Promise
的狀態取決於最早改變狀態的 Promise
實例的狀態。
在前端最典型的一個用法是爲 fetch api 模擬請求超時。
Promise.race([ fetch("https://cnodejs.org/api/v1/topics?tab=good&limit=10").then(res => res.json() ), new Promise((resolve, reject) => { setTimeout(reject, 1, "error"); }) ]) .then(result => { console.info("then", result); }) .catch(error => { console.info("catch", error); // 進入這裏 });
上述例子中只要請求 未在 1 毫秒內結束就會進入 .catch()
方法中,雖然不能將請求取消,可是超時模擬卻成功了
Promise.resolve(value)
&& Promise.reject(reason)
這兩個方法都能用來建立並返回一個新的 Promise , 區別是 Promise.resolve(value)
攜帶進新的 Promise 狀態是 fulfilled
。而 Promise.reject(reason)
帶來的 rejected
有的時候能夠用來簡化一些建立 Promise 的操做如:
const sleep = (time = 0) => new Promise(resolve => setTimeout(resolve, time)); // 這裏建立一個 睡眠,而且打印的鏈 Promise.resolve() .then(() => { console.log(1); }) .then(() => sleep(1000)) .then(() => { console.log(2); }) .then(() => sleep(2000)) .then(() => { console.log(3); });
有時也用來 手動改變 Promise 鏈中的返回狀態 ,固然這樣實際上和 直接返回一個值,或者是 使用 throw Error 來構造一個錯誤,並沒有區別。到底要怎麼用 就看我的喜愛了
new Promise((resolve, reject) => { setTimeout(() => { resolve("resolve"); // 1. }, 1000); }) .then(result => { return Promise.reject("reject1"); // 2. }) .then( result => { return Promise.resolve(result + "2"); }, err => { return Promise.resolve(err); // 3. } ) .then(res => { console.log(res); // 4. }) .catch(err => { console.log(err + "err"); }); // reject1
下面來看幾個例子:
關於執行順序,具體可搜索,js 循環
new Promise((resolve, reject) => { console.log("step 1"); resolve(); console.log("step 2"); }).then(() => { console.log("step 3"); }); console.log("step 4"); // step 1, step 2, step 4 , step 3
在使用 Promise 構造函數構造 一個 Promise 時,回調函數中的內容就會當即執行,而 Promise.then
中的函數是異步執行的。
關於狀態不可變動
let start; const p = new Promise((resolve, reject) => { setTimeout(() => { start = Date.now(); console.log("once"); resolve("success"); }, 1000); }); p.then(res => { console.log(res, Date.now() - start); }); p.then(res => { console.log(res, Date.now() - start); }); p.then(res => { console.log(res, Date.now() - start); });
Promise
構造函數只執行一次,內部狀態一旦改變,有了一個值,後續不論調用多少次then()
都只拿到那麼一個結果。
關於好像狀態能夠變動
const p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve("success"); }, 1000); }); const p2 = p1.then((resolve, reject) => { throw new Error("error"); }); console.log("p1", p1); console.log("p2", p2); setTimeout(() => { console.log("p1", p1); console.log("p2", p2); }, 2000);
觀察這一次的打印
第一次打印出兩個 Promise
的時候都是 pending
,由於 p2 是基於 p1 的結果,p1 正在 pending ,當即打印出的時候確定是 pending ;第二次打印的時候,由於 p1 的狀態爲 resolved ,p2 爲 rejected ,這個並非已經爲 fulfilled 狀態改變爲 rejected ,而是 p2 是一個新的 Promise 實例,then()
返回新的 Promise 實例。
關於透傳
Promise.resolve(11) .then(1) .then(2) .then(3) .then(res => { console.info("res", res); }); // 11
給 then 方法傳遞了一個非函數的值,等同於 then(null)
,會致使穿透的效果,就是直接過掉了這個 then() ,直到符合規範的 then() 爲止。
使用 Array.reduce 方法串行執行 Promise
const sleep = (time = 0) => new Promise(resolve => setTimeout(resolve, time)); [1000, 2000, 3000, 4000].reduce((Promise, item, index) => { return Promise.then(res => { console.log(index + 1); return sleep(item); }); }, Promise.resolve()); // 在分別的等待時間後輸出 1,2,3,4
這篇文章到這裏就基本上結束了,相信 若是能理解上面的內容,而且在實際項目中使用的話。應該會讓工做更高效吧,對於新的異步使用應該也會更加的駕輕就熟。Promise 的使用相對簡單,可能後續再出一篇如何實現一個 Promise 吧
那些收集的 Promise 的優質文章。