解決回調地獄問題(代碼臃腫、可讀性差、耦合度太高、複用性差)前端
Promise是異步編程的一種解決方案,promise異步回調,能夠避免層層嵌套回調。編程
Promise對象是一個構造函數,用來生成Promise實例。Promise的構造函數接收一個參數,是函數,而且傳入兩個參數:resolve,reject,分別表示異步操做執行成功後的回調函數和異步操做執行失敗後的回調函數。數組
Promise有三種狀態:pending(執行狀態)、fulfilled(成功時的狀態)、rejected(失敗時的狀態)。當promise狀態發生改變,就會觸發then()裏面相應的回調函數處理後續步驟,當promise狀態已經改變,則不會更改。promise
var p = new Promise(function(resolve, reject){ //作一些異步操做 setTimeout(function(){ console.log('執行完成'); resolve('隨便什麼數據'); }, 2000); });
運行代碼,會在2秒後輸出「執行完成」。注意!我只是new了一個對象,並無調用它,咱們傳進去的函數就已經執行了,這是須要注意的一個細節。因此咱們用Promise的時候通常是包在一個函數中,在須要的時候去運行這個函數,如:瀏覽器
function runAsync(){ var p = new Promise(function(resolve, reject){ //作一些異步操做 setTimeout(function(){ console.log('執行完成'); resolve('隨便什麼數據'); }, 2000); }); return p; } runAsync().then(function(data){ console.log(data); //後面能夠用傳過來的數據作些其餘操做 //...... });
咱們包裝好的函數最後,會return出Promise對象,也就是說,執行這個函數咱們獲得了一個Promise對象。在runAsync()的返回上直接調用then方法,then接收一個參數,是函數,而且會拿到咱們在runAsync中調用resolve時傳的的參數。運行這段代碼,會在2秒後輸出「執行完成」,緊接着輸出「隨便什麼數據」。dom
(1)Promise.then():用於註冊狀態變爲fulfilled或者reject時的回調函數,接受兩個參數,第一個對應resolve的回調,第二個對應reject的回調。then()方法返回的是一個新的Promise實例,所以能夠採用鏈式寫法。異步
const promise = new Promise((resolve, reject) => { resolve('fulfilled'); // 狀態由 pending => fulfilled }); promise.then(result => { // onFulfilled console.log(result); // 'fulfilled' }, reason => { // onRejected 不會被調用 })
(2)Promise.catch():在鏈式寫法中能夠捕獲then中發送的異常。async
通常來講,不要在then()裏面定義rejected狀態的回調函數(即then的第二個參數),而應該使用catch()方法。理由是catch()方法能夠捕獲前面then()方法執行中的錯誤,也更接近同步的寫法(try/catch)。 異步編程
和傳統的try/catch代碼塊不一樣的是,若是沒有使用catch()方法指定錯誤處理的回調函數,Promise對象拋出的錯誤不會被傳遞到代碼外層,即不會有任何反應。函數
在someAsyncThing函數中產生Promise對象,內部有語法錯誤,瀏覽器運行到這一步會打印錯誤,可是並不會退出進程、終止腳本執行,2s後仍是輸出123。也就是說Promise內部的錯誤並不會影響到Promise外部的代碼,即「Promise會吃掉錯誤」。
(3)Promise.all():能夠將多個Promise實例包裝成一個新的Promise實例。多個Pomise任務同時執行,若是所有執行成功,則以數組的方式返回全部Promise任務的執行結果。若是有一個Promise任務rejected,則只返回第一個rejected任務的結果。
Promise.all([p1,p2,p3])接受一個數組做爲參數,數組中的p1,p2,p3都是promise實例/。
let p1=new Promise((resolve,reject)=>{ resolve('p1成功'); }) let p2=new Promise((resolve,reject)=>{ resolve('p2成功'); }) let p3=Promise.reject('p3失敗'); Promise.all([p1,p2]).then((res)=>{ console.log(res);// ['p1成功','p2成功'] }).catch((err)=>{ console.log(err); }) Promise.all([p1,p2,p3]).then((res)=>{ console.log(res);//p3失敗 }).catch((err)=>{ console.log(err); })
注意:
Promise.all()得到的成功結果的數組裏面的數據順序與Promise.all()接收參數的順序是一致的,即P1的結果在前,即便P1的結果獲取的比P2晚。
這帶了一個很大的好處:在前端開發請求數據的過程當中,偶爾會遇到發送多個異步請求要求按照發送的順序獲取數據,使用Promise.all()能夠解決這個問題。
(4)Promise.race():race賽跑的意思,多個Prromise任務同時執行,返回最早執行結束的Promise任務的結果,無論這個Promise結果是成功仍是失敗。
注意:Promise.race在第一個Promise對象變爲fulfilled以後,並不會取消其餘promise對象的執行。只是只有先完成的Promise纔會被Promice.race後面的then()處理,其餘promise仍是在執行的,只不過不會進入到promise.race後面的then裏。
let p1=new Promise((resolve,reject)=>{ setTimeout(function(){ resolve('p1成功'); },2000) }) let p2=new Promise((resolve,reject)=>{ setTimeout(function(){ resolve('p2成功'); }) },500) Promise.race([p1,p2]).then((res)=>{ console.log(res);//p2成功 }).catch((err)=>{ console.log(err); })
async關鍵字
async function asyncFn() { return 'hello world'; } asyncFn();
返回結果:
關於async/await的使用規則:
(1)async函數的返回類型爲Promise對象。
當返回的是promise對象時,正好符合async函數的返回類型;
當返回的是值類型時,至關於Promise.resolve(data);仍是一個Promise對象,可是在調用async函數的地方經過簡單的=是拿不到這個data返回值的,由於返回值是一個Promise對象,須要用.then(data={console.log(data)})函數才能拿到。
若是沒有返回值,至關於返回了Promise.resolve(undefined);
(2)非阻塞
async函數裏面若是有異步過程會等待,可是async函數自己會立刻返回,不會阻塞當前線程,能夠理解爲,async函數工做在主線程,同步執行,不會阻塞頁面渲染。async內部由await關鍵字修飾的異步過程,工做在相應的協程上,會阻塞等待異步任務的完成再返回。
(3)無等待
async代表程序中可能有異步過程,async的異步體如今await上。在沒有await的狀況下執行async函數,他會當即執行,返回一個Promise對象,而且不會阻塞後面的語句。
(4)await必須放在async函數內部使用,不能單獨使用;
(5)await關鍵字後面跟Promise對象,async函數必須等到內部的全部await命令的Promise對象執行完,纔會發生狀態改變。
await後面的promise對象沒必要寫then,由於await的做用之一就是獲取後面promise對象成功狀態傳遞出來的參數。
執行過程:
await是讓出線程的標誌,當執行到await時,await後面的函數會先執行一遍,而後再跳出整個async函數來執行後面的JS棧代碼。等本輪時間循環執行完了以後又跳回到async函數中等待await後面表達式的返回值,若是返回值爲非Promise則繼續執行async後面的代碼,不然將返回的Promise放入Promise隊列。
async/await的錯誤處理方法
await能夠直接獲取後面promise對象成功狀態傳遞的參數,可是卻捕捉不到失敗的狀態。在這裏,經過給async函數添加then()/catch()方法來解決,由於async函數自己就會返回一個promise對象。
一個包含錯誤處理的完整例子:
function fun2(){ return new Promise((resolve,reject)=>{ var num=Math.random(); if(num<=0.5){ resolve('success'); } else{ reject('failed'); } }) } async function fun1(){ console.log('async函數'); let res=await fun2(); //注意:在async函數裏,必需要將await的結果return出去,不然的話執行then()返回值都爲undefined return res; } fun1().then(data=>{ console.log(data); }).catch(error=>{ console.log(error) })
(1)能更好地處理then()鏈式調用,代碼更加優雅,幾乎和同步代碼同樣。
(2)簡潔。使用Async/await明顯節省很多代碼,咱們不須要寫.then,不須要寫匿名函數處理promise的resolve值,也不須要定義多餘的data變量。
(3)中間值
假如調用promise1,而後使用promise1的返回值去調用promise2,而後使用二者的結果去調用promise3。
使用promise:
function promise1(){ return new Promise((resolve,reject)=>{ resolve('p1'); }) } function promise2(val){ return new Promise((resolve,reject)=>{ resolve(val); }) } function promise3(val1,val2){ return new Promise((resolve,reject)=>{ resolve(val1+","+val2); }) } function fun(){ return promise1().then((v1)=>{ return promise2(v1).then((v2)=>{ return promise3(v1,v2); }) }) } fun().then((data)=>{ console.log(data); })
使用async/await
async function fun2(){ let v1=await promise1(); let v2=await promise2(v1); return promise3(v1,v2); } fun2().then((data)=>{ console.log(data); })
(1)錯誤會被吃掉
throw new Error('error'); console.log(‘last’); //因爲throw error的緣故,代碼被終止,因此不會輸出'last'。 對於promise let promise = new Promise(() => { throw new Error('error') }); console.log(‘last’);//會正常輸出‘last’
說明Promise內部的錯誤不會影響到Promise外部的代碼,這種狀況一般稱爲「吃掉錯誤」。
這並非Promise獨有的侷限性,try...catch也是這樣,一樣會捕捉一個異常並簡單的吃掉錯誤。
正是由於錯誤被吃掉,因此Promise中的錯誤很容易被忽略掉,這也是爲何通常推薦在Promise鏈的最後添加一個catch方法。
(2)沒法取消
Promise一旦建立就會當即執行,沒法中途取消。
(3)沒法得知pending狀態。
當處於pending狀態時,沒法得知目前進展到哪個階段(剛剛開始仍是即將完成)。
以上基本能夠實現簡單的同步代碼,即改變狀態操做使用的是同步,可是當resolve在setTimeout內執行時,then時的state仍是pending等待狀態,這就須要咱們在then調用的時候,將成功和失敗存到各自的數組,一旦reject或者resolve就調用他們。
完整代碼(只能實現一個then,還不能鏈式調用)
class Promise1{ constructor(fn){ //定義三個狀態 this.state='pending';//初始狀態 this.value=undefined;//成功的值 this.reason=undefined;//失敗的緣由 //成功存放的數組 this.onResolvedArr=[]; //失敗存放的數組 this.onRejectArr=[]; let resolve=(value)=>{ //用箭頭函數改變this指向 if(this.state=='pending'){ this.state='fulfilled'; this.value=value; //一旦執行resove,調用成功數組的函數 this.onResolvedArr.forEach(fun=>fun()) } }; let reject=(reason)=>{ if(this.state=='pending'){ this.state='rejected'; this.reason=reason; this.onRejectArr.forEach(fun=>fun()) } } //自動執行函數 try{ fn(resolve,reject) }catch(e){ reject(e); } } then(onFulfilled,onRejected){ //狀態爲成功時執行onFulfilled方法並傳值 if(this.state=='fulfilled'){ onFulfilled(this.value); } //狀態爲失敗時執行onRejected方法 if(this.state=='rejected'){ onRejected(this.reason); } //當state的狀態爲pending時將要執行的方法放到數組中和,再調用resolve或reject時執行。 if(this.state=='pending'){ this.onResolvedArr.push(()=>{ onFulfilled(this.value); }) this.onRejectArr.push(()=>{ onRejected(this.reason); }) } } } var p=new Promise1(function(resolve,reject){ setTimeout(function(){ resolve('成功'); },1000) }) p.then(function(data){ console.log(data);//成功 },function(err){ console.log(err) })
Promise.then()的鏈式調用是經過返回一個新的Promise實現的。
既然jQuery經過返回this實現鏈式調用,爲何promise.then()不返回this實現呢?
如:
var promise2 = promise1.then(function (value) { return Promise.reject(3) })
假如then函數執行返回this調用對象自己,那麼promise2==promise1,promise2的狀態也應該等於promise1的狀態同爲resolved。而onResolved回調函數中返回的是rejected對象,由於Promise中的狀態一旦改變就不能更改,因此promise2沒辦法轉成回調函數返回的rejected狀態,產生矛盾。
每一個promise.then()都返回一個新的promise,返回的新的promise會在上一個狀態發生改變後執行,依此類推,每次返回的新的promise被掛在上一個promise上,造成一個串。
function promiseAll(promises){ if(!Array.isArray(promises)){ throw new TypeError('argument must be array'); } return new Promise((resolve,reject)=>{ let len=promises.length; let index=0; var res=[]; for(let i=0;i<len;i++){//注意定義爲let,保留當前的i promises[i].then((data)=>{//執行參數中的promise實例 index++; res[i]=data;//把每個promise的執行成功結果放入數組中 不能用push()方法,保證返回的順序 if(index==len){//說明全部promise都執行成功 return resolve(res); } },function(err){ return reject(err); }) } }) } var a1=Promise.resolve(1); var a2=Promise.resolve(2); var a3=new Promise((resolve,reject)=>{ resolve('a3'); }) promiseAll([a1,a2,a3]).then(function(res){ console.log(res);//[1,2,'a3'] }).catch(function(err){ console.log(err); })
function Race(promise){ return new Promise((resolve,reject)=>{ for(let i=0;i<promise.length;i++){ promise[i].then(resolve,reject); } }) } var p1=new Promise((resolve,reject)=>{ setTimeout(function(){ resolve('2'); },2000) }) var p2=new Promise((resolve,reject)=>{ setTimeout(function(){ resolve('3'); },1000) }) Race([p1,p2]).then(function(val){ console.log(val);//3 })
題目:紅燈三秒亮一次,綠燈一秒亮一次,黃燈2秒亮一次;如何讓三個燈不斷交替重複亮燈?(用 Promse 實現)
咱們想要在resolve的狀況下中斷或者終止鏈式調用,主要方法是基於Promise的特色:原Promise對象的狀態和新對象保持一致。
咱們僅須要在鏈式調用中,返回一個pending狀態或者rejected狀態的Promise對象便可。
return (new Promise((resolve, reject)=>{}));//返回pending狀態
return (new Promise((resolve, reject)=>{rejcet()}));//返回reject狀態 會被最後catch捕獲。
.then((number)=>{ console.info(’在這裏中斷‘); return new Promise((resolve,reject)=>{})//pending狀態 //或者 return new Promise((resolve,reject)=>{reject()})//reject()狀態 }).then((number)=>{ console.info(`fun4 result:${number}`); })