一篇文章徹底搞懂es6 Promise
前言
Promise,用於解決回撥地獄帶來的問題,將非同步操作以同步的操作程式設計表達出來,避免了層層巢狀的回撥函式。
既然是用來解決回撥地獄的問題,那首先來看下什麼是回撥地獄
var sayhello = function(callback){ setTimeout(function(){ console.log("hello"); return callback(null); },1000); } sayhello(function(err){ console.log("xiaomi"); }); console.log("mobile phone"); 輸出結果 mobile phone hello Xiaomi
看上面這段程式碼,假如我們需要對輸出內容的順序進行調整,例如依次列印xiaomi apple huawei ,那麼我們之前的做法是怎麼樣的
var sayhello = function(name, callback){ setTimeout(function(){ console.log("hello"); console.log(name); return callback(null); },1000); } sayhello("xiaomi", function(err){ sayhello("apple", function(err){ sayhello("huawei", function(err){ console.log("end"); }); }); }); console.log("mobile phone");
問題很明顯,程式碼層層巢狀,看起來十分的混亂,如果層級程式碼更多更是難以維護
因此Promise的出現使得我們可以用同步的方式來操作非同步程式碼,解決以上問題
初識promise
var getUserInfo = function() { return new Promise(function(resolve) { setTimeout(function() { var user = {name: 'Kerry Wu', age: 31}; resolve(user); }, 3000); }); }; this.getUserInfo().then(function(userInfo) { console.log('userInfo',userInfo);//{ name: 'Hanmeimei', age: 31 } });
我們通過new關鍵詞例項化一個Promise物件並返回該物件,然後使用.then的形式獲取Promise返回的內容
這裡需要注意的是,new Promise 例項化是一個同步的過程,而.then是一個非同步的過程,關於同步非同步執行順序 ,先執行同步在執行非同步程式碼
Promise狀態
1、Pending 進行中 / Resolved 已成功 / Rejected 已失敗
resove 將未完成變成已完成 pending => resolved
reject 將未完成變成已失敗 pending => rejected
var promise = new Promise(function(resolve, reject) { // ... some code if (/* 非同步操作成功 */){ resolve(value); } else { reject(error); } });
2、then 與 catch
then方法接收兩個函式引數,第一個表示resove 已成功的回撥,第二個表示reject 已失敗的回撥
用法如下:
var p = new Promise(function(resolve, reject){ ... }) p.then(function(){}, function(){}) p.then().catch();
then,前一個then的返回結果,可以再後一then的回撥中獲取,如:
var p3 = ()=> new Promise((resolve, reject)=>{ resolve('{"name":"jack", "age":28}') }); p3() .then(res => JSON.parse(res)) .then(data => Object.assign(data, {name:'rose'})) .then(data => console.log(data)) // 輸出:{name: "rose", age: 28}
非同步載入圖片
function loadImageAsync(url) { return new Promise(function(resolve, reject) { var image = new Image(); image.onload = function() { resolve(image); }; image.onerror = function() { reject(new Error('Could not load image at ' + url)); }; image.src = url; }); } loadImagesAsync('//img.static.com/xxx.jpg').then(function(img){ //載入成功 顯示圖片 }, function(err){ //載入失敗 提示失敗 })
非同步載入資料
使用promise包裝一個非同步請,返回一個promise物件,使用then和catch的方式對返回結果進行處理
var getJSON = function(url){ return new Promise((resolve, reject)=>{ var client = new XMLHttpRequest(); client.open('GET', url); client.onreadystatechange = callback; client.send(); function callback(){ if(this.readyState !== 4) return; if(this.status === 200){ resolve(this.response) }else{ reject(new Error(this.statusText)) } } }) } getJSON('/api/getList').then(function(data){ //獲取請求的資料 }, function(err){ //請求失敗錯誤處理 });
catch
p.catch()用於處理promise中rejected狀態的回撥,與p.then(resolveFn, rejectFn)中 rejectFn的作用相同
var p = new Promise(function(resolve, reject){ ... }); p.then(function(){}, function(){}); //等同於 p.then(function(){}).catch(function(){});
reject(‘error’) 與 throw new Error(‘…’) 都能被catch捕獲
new Promise((resolve, reject) => { throw new Error('some error1'); }).catch(err =>console.log(err.message)) // 等同於 new Promise((resolve, reject) => { reject('some error2') }).catch(err => console.log(err))
捕獲異常
promise物件的錯誤,具有 冒泡 性質,會一直向後傳遞,直到被捕獲
推薦使用 catch 代替then(null, rejectFn)中的rejectFn,catch可以捕獲前面then函式返回的錯誤資訊,也更接近同步的寫法
// bad new Promise(function(resolve, reject){}).then(resolveFn, rejectFn) // good new Promise(function(resolve, reject){}).then(resoveFn).catch(rejectFn)
Promise all與race
Promise.all([]) 與 Promise.race([])
- 接收一個數組做為引數,引數中的每個元素為promise例項,
- 如果元素不是promise例項,則會呼叫Promise.resolve()轉換為promise的例項
- 將多個promise物件包裝為一個新的promise物件
1、Promise.all()
Promise的all方法提供了並行執行非同步操作的能力,並且在所有非同步操作執行完後才執行回撥
當p1、p2、p3的狀態全部為resolved時,才能將p的狀態改為resolved
當p1、p2、p3其中一個狀態變成rejected時,就會將p的狀態變成rejected
var p = Promise.all([Promise.resolve('1'), Promise.resolve('2'), Promise.resolve('3')]); p.then(data => console.log(data)) //["1", "2", "3"] var p1 = Promise.all([Promise.resolve('1'), Promise.reject('2'), Promise.resolve('3')]); p1.then(data => console.log(data)).catch(err => console.log(err)) // 2
Promise.all用的最多一般是我們在請求網路資料時,比如需要同時請求多個介面,我們可以合併多個請求一次處理
function getURL(URL) { return new Promise(function (resolve, reject) { var req = new XMLHttpRequest(); req.open('GET', URL, true); req.onload = function () { if (req.status === 200) { resolve(req.responseText); } else { reject(new Error(req.statusText)); } }; req.onerror = function () { reject(new Error(req.statusText)); }; req.send(); }); } function getComment() { return getURL('http://azu.github.io/promises-book/json/comment.json').then(res=>JSON.parse(res)); } function getPeople() { return getURL('http://azu.github.io/promises-book/json/people.json').then(res=>JSON.parse(res)); } // 合併請求 Promise.all([getComment(), getPeople()]).then(function (value) { console.log(value); }).catch(function(error){ console.log(error); });
Promise resolve和reject
Promise.resolve() 與 Promise.reject()
Promise.resolve('foo') // 等價於 new Promise(resolve => resolve('foo'))
最後
記得以前在面試的時候,被問了一道很有意思的面試題,主要是考察promise和settimeout執行順序
setTimeout(function () { console.log(1) }, 0); new Promise(function executor(resolve) { resolve(); }).then(function () { console.log(2); });
如上程式碼,為什麼執行結果是2,1而不是1,2?
不是setTimeout先加入任務佇列嗎?
解答:
1、從規範上來講,setTimeout有一個4ms的最短時間,也就是說不管你設定多少,反正最少都要間隔4ms才執行裡面的回撥(當然,瀏覽器有沒有遵守這個規範是另外一回事兒)。而Promise的非同步沒有這個問題。
2、從具體實現上來說,這倆的非同步佇列不一樣,Promise所在的那個非同步佇列優先順序要高一些。
還有一道差不多的
(function test() { setTimeout(function() {console.log(4)}, 0); new Promise(function executor(resolve) { console.log(1); for( var i=0 ; i<10000 ; i++ ) { i == 9999 && resolve(); } console.log(2); }).then(function() { console.log(5); }); console.log(3); })()
為什麼輸出結果是 1,2,3,5,4 而非 1,2,3,4,5 ?
解答:
1、Promise.then 是非同步執行的,而建立Promise例項( executor )是同步執行的。
2、setTimeout 的非同步和 Promise.then 的非同步不太一樣不在同一個佇列中,setTimeout(fn, 0)在下一輪“事件迴圈”開始時執行,Promise.then()在本輪“事件迴圈”結束時執行。因此then 函式先輸出,settimeout後輸出。
這裡涉及到js事件迴圈、任務佇列的東西,瞭解更多https://www.cnblogs.com/hity-tt/p/6733062.html