promise是異步編程的一種解決方案,它出現的初衷是爲了解決回調地獄的問題。html
打個比方,我須要:es6
--(延遲1s)--> 輸出1 --(延遲2s)--> 輸出2 --(延遲3s)--> 輸出3
一般寫法:編程
setTimeout(()=> { console.log('1'); setTimeout(()=> { console.log('2'); setTimeout(()=> { console.log('3'); }, 3000) }, 2000) }, 1000)
這樣的多重的嵌套的回調被稱爲回調地獄,這樣的代碼可讀性不好,不利於理解。segmentfault
若是用promise的話畫風一轉數組
function delay(time, num) { return new Promise((res, rej)=> { setTimeout(()=> { console.log(num); res(); }, time*1000) }); } delay(1, 1).then(()=> { return delay(2, 2); }).then(()=> { delay(3, 3); })
使用了promise的鏈式調用,代碼結構更清晰。
是否是很棒?那還不趕快get起來~promise
調用方式以下:app
new Promise((resolve, reject)=> { if('some option') { resolve('some value'); } else { reject('some error'); } }).then( val=> { // ...
}, error=> { // ...
} )
Promise構造函數接收一個函數型參數fn,fn有兩個參數,分別是:resolve、reject,Promise還有一個Promise.prototype.then方法,該方法接收兩個參數,分別是成功的回調函數succ和失敗的回調函數error。異步
在fn中調用resolve會觸發then中的succ回調,調用reject會觸發error回調。async
new Promise((res, rej)=> { res('happy') }).then(val=> { console.log(val); // happy
}); new Promise((res, rej)=> { rej('error!'); }).then(val=> {}, err=> { console.log(err); // error!
});
new Promise((res, rej)=> { res('a'); }).then(val=> { return 'b' }).then(val=> { console.log(val); // 'b'
}).then((val)=> { console.log(val); // 'undefined'
});
new Promise((res, rej)=> { res('a'); }).then(val=> { return 'b'; }).then(val=> { console.log(val); // 'b'
return 'c'; }).then({ // 並不是函數
name: 'lan' }).then((val)=> { console.log(val); // 'c'
});
let doSomething = function() { return new Promise((resolve, reject) => { resolve('返回值'); }); }; let doSomethingElse = function() { return '新的值'; } doSomething().then(function () { return doSomethingElse(); }).then(resp => { console.warn(resp); console.warn('1 =========<'); }); doSomething().then(function () { doSomethingElse(); }).then(resp => { console.warn(resp); console.warn('2 =========<'); }); doSomething().then(doSomethingElse()).then(resp => { console.warn(resp); console.warn('3 =========<'); }); doSomething().then(doSomethingElse).then(resp => { console.warn(resp); console.warn('4 =========<'); });
結合上面的講解想想會輸出什麼?(答案及解析)異步編程
當Promise中的狀態(pending ---> resolved or rejected)發生變化時纔會執行then方法。
new Promise((res, rej)=> { res('a'); }).then(val=> { return 'b'; }); // 等同於
new Promise((res, rej)=> { res('a'); }).then(val=> { return new Promise((res, rej)=> { res('b'); }); });
new Promise((res, rej)=> { console.log('a'); res(''); }).then(()=> { console.log('b'); }); console.log('c'); // a c b
new Promise((res, rej)=> { console.log('a'); }).then(()=> { console.log('b'); }); console.log('c'); // a c
Promise還有四個靜態方法,分別是resolve、reject、all、race,下面咱們一一介紹一下。
除了經過new Promise()的方式,咱們還有兩種建立Promise對象的方法,Promise.resolve()至關於建立了一個當即resolve的對象。以下兩段代碼做用相同:
Promise.resolve('a'); new Promise((res, rej)=> { res('a'); });
固然根據傳入的參數不一樣,Promise.resolve()也會作出不一樣的操做。
若是參數是 Promise 實例,那麼Promise.resolve將不作任何修改、原封不動地返回這個實例。
thenable對象指的是具備then方法的對象,好比下面這個對象。
let thenable = { then: function(resolve, reject) { resolve(42); } };
Promise.resolve方法會將這個對象轉爲 Promise對象,而後就當即執行thenable對象的then方法。
若是參數是一個原始值,或者是一個不具備then方法的對象,則Promise.resolve方法返回一個新的 Promise 對象,狀態爲resolved。
Promise.resolve方法容許調用時不帶參數,直接返回一個resolved狀態的 Promise 對象。
值得注意的一點是該靜態方法是在本次事件輪詢結束前調用,而不是在下一次事件輪詢開始時調用。關於事件輪詢能夠看這裏——>JavaScript 運行機制詳解:再談Event Loop
和Promise.resolve()相似,只不過一個是觸發成功的回調,一個是觸發失敗的回調
Promise的all方法提供了並行執行異步操做的能力,而且在全部異步操做執行完後才執行回調。
function asyncFun1() { return new Promise((res, rej)=> { setTimeout(()=> { res('a'); }, 1000); }); } function asyncFun2() { return new Promise((res, rej)=> { setTimeout(()=> { res('b'); }, 1000); }); } function asyncFun3() { return new Promise((res, rej)=> { setTimeout(()=> { res('c'); }, 1000); }); } Promise.all([asyncFun1(), asyncFun2(), asyncFun3()]).then((val)=> { console.log(val); }); Promise.all([asyncFun1(), asyncFun2(), asyncFun3()]).then((val)=> { console.log(val); // ['a', 'b', 'c']
});
用Promise.all來執行,all接收一個數組參數,裏面的值最終都算返回Promise對象。這樣,三個異步操做的並行執行的,等到它們都執行完後纔會進到then裏面。有了all,你就能夠並行執行多個異步操做,而且在一個回調中處理全部的返回數據。
適用場景:打開網頁時,預先加載須要用到的各類資源如圖片、flash以及各類靜態文件。全部的都加載完後,咱們再進行頁面的初始化。
race()和all相反,all()是數組中全部Promise都執行完畢就執行then,而race()是一旦有一個Promise執行完畢就會執行then(),用上面的三個Promise返回值函數舉例
Promise.race([asyncFun1(), asyncFun2(), asyncFun3()]).then((val)=> { console.log(val); // a
});
看了這麼多關於Promise的知識,咱們來作一道題鞏固一下。
寫一個類Man實現如下鏈式調用 調用方式: new Man('lan').sleep(3).eat('apple').sleep(5).eat('banana');
打印: 'hello, lan' -(等待3s)--> 'lan eat apple' -(等待5s)--> 'lan eat banana'
思路:
具體實現以下:
class Man { constructor(name) { this.name = name; this.sayName(); this.rope = Promise.resolve(); // 定義全局Promise做鏈式調用
} sayName() { console.log(`hello, ${this.name}`); } sleep(time) { this.rope = this.rope.then(()=> { return new Promise((res, rej)=> { setTimeout(()=> { res(); }, time*1000); }); }); return this; } eat(food) { this.rope = this.rope.then(()=> { console.log(`${this.name} eat ${food}`); }); return this; } } new Man('lan').sleep(3).eat('apple').sleep(5).eat('banana');
ok!不知道你有沒有看懂呢?若是能徹底理解代碼那你的Promise能夠通關了,順便來個小思考,下面這種寫法能夠嗎?和上面相比有什麼區別?:
class Man1345 { constructor(name) { this.name = name; this.sayName(); } sayName() { console.log(`hello, ${this.name}`); } sleep(time) { this.rope = new Promise((res, rej)=> { setTimeout(()=> { res(); }, time*1000); }); return this; } eat(food) { this.rope = this.rope.then(()=> { console.log(`${this.name} eat ${food}`); }); return this; } } new Man('lan').sleep(3).eat('apple').sleep(5).eat('banana');
簡單的說,第二段代碼的執行結果是
'hello, lan' -(等待3s)--> 'lan eat apple' ---> 'lan eat banana'
爲何會出現這種差異? 由於第二段代碼每一次調用sleep都會new一個新的Promise對象,調用了兩次sleep就new了兩個Promise對象。這兩個對象是異步並行執行,會形成兩句eat同時顯示。
和如下狀況相似
var time1 = setTimeout(()=> { console.log('a'); }, 1000) var time2 = setTimeout(()=> { console.log('b'); }, 1000) // 同時輸出 a b
抽象一點的講解是:
// 第一段正確的代碼的執行爲
var p1 = new Promise().then('停頓3s').then('打印食物').then('停頓5s').then('打印食物'); // 第二段代碼的執行行爲,p一、p2異步並行執行
var p1 = new Promise().then('停頓3s').then('打印食物'); var p2 = new Promise().then('停頓5s').then('打印食物'); 總結
Promise的常常用到的地方:
Promise是咱們的好幫手,不過還有另外一種方法也能夠作到,那就是async&await,能夠多多瞭解一下。
參考資料