一、什么是Promise?我們用Promise來解決什么問題?
為什么有Promises這個東西
- 同步的方式寫異步的代碼,用來解決回調地獄問題。
- 此外,promise對象提供統一的接口,使得控制異步操作更加容易。
什么是Promise?
- Promise,簡單說就是一個
容器
,里面保存着某個未來才會結束的事件(通常是一個異步操作)的結果。 - 從語法上說,promise 是一個
對象
,從它可以獲取異步操作的的最終狀態(成功或失敗)。 - Promise是一個
構造函數
,對外提供統一的 API,自己身上有all、reject、resolve等方法,原型上有then、catch等方法。
Promise的兩個特點
-
Promise對象的狀態不受外界影響
1)pending 初始狀態
2)fulfilled 成功狀態
3)rejected 失敗狀態
Promise 有以上三種狀態,只有異步操作的結果可以決定當前是哪一種狀態,其他任何操作都無法改變這個狀態
-
Promise的狀態一旦改變,就不會再變,任何時候都可以得到這個結果,狀態不可以逆,只能由 pending變成fulfilled或者由pending變成rejected
使用 new 來創建一個promise對象。
Promise接受一個「函數」作為參數,該函數的兩個參數分別是resolve
和reject
。這兩個函數就是就是「回調函數」
resolve函數的作用:在異步操作成功時調用,並將異步操作的結果,作為參數傳遞出去;
reject函數的作用:在異步操作失敗時調用,並將異步操作報出的錯誤,作為參數傳遞出去。
const promise = new Promise((resolve, reject) => { // do something here ... if (success) { resolve(value); // fulfilled } else { reject(error); // rejected } });
Promise的API
then()方法
then 方法就是把原來的回調寫法分離出來,在異步操作執行完后,用鏈式調用的方式執行回調函數。
而 Promise 的優勢就在於這個鏈式調用。我們可以在 then 方法中繼續寫 Promise 對象並返回,然后繼續調用 then 來進行回調操作。
可有兩個參數,第一個是成功 resolve 調用的方法,第二個是失敗 reject 調用的方法
下面做一個買筆寫作業上交的演示,它們是層層依賴的關系,下一步的的操作需要使用上一部操作的結果。(這里使用 setTimeout 模擬異步操作),正式開發可以用 ajax 異步
//買筆 function buy(){ console.log("開始買筆"); var p = new Promise(function(resolve,reject){ setTimeout(function(){ console.log("買了筆芯"); resolve("數學作業"); },1000); }); return p; } //寫作業 function work(data){ console.log("開始寫作業:"+data); var p = new Promise(function(resolve,reject){ setTimeout(function(){ console.log("寫完作業"); resolve("作業本"); },1000); }); return p; } function out(data){ console.log("開始上交:"+data); var p = new Promise(function(resolve,reject){ setTimeout(function(){ console.log("上交完畢"); resolve("得分:A"); },1000); }); return p; } /* 不建議使用這種方式 buy().then(function(data){ return work(data); }).then(function(data){ return out(data); }).then(function(data){ console.log(data); });*/ //推薦這種簡化的寫法 buy().then(work).then(out).then(function(data){ console.log(data); });
正式開發用ajax異步:
var promise = new Promise(function(resolve,reject){ $.ajax({ url:'/api/poisearch.json', method:'get', datatype:'json', success:(res) =>{ resolve(res) }, error:(err)=>{ reject(err) } }); }); promise.then(function(res){ return res.data }).then(function(data){ return data.result; }).then(function(result){ console.log(result) }); //推薦使用箭頭函數簡寫成,極大提升了代碼的簡潔性和可讀性 promise.then(res => res.data).then(data => data.result).then(result => console.log(result));
Promise構造函數的超能力
Promises寫法的本質就是把異步寫法寫成同步寫法。傳入Promise構造函數的函數參數會第一優先執行,無論這個函數多么的繁復,有多少層回調,有多少秒的計數器,統統都會最優先執行。
也就是說,我們只要new了一個Promise(),那么Promise構造函數的函數參數其實是同步代碼,但是.then比較特殊,.then會等到promise對象實例有了結果(resolved或者rejected),.then()里面代碼才會執行。鏈條上的每一個.then都會等前面的promise有了結果才會執行,Promise構造函數的這個超能力是Promises系統的威力之源。
reject()方法:
上面樣例我們通過 resolve 方法把 Promise 的狀態置為完成態(Resolved),這時 then 方法就能捕捉到變化,並執行“成功”情況的回調。
而 reject 方法就是把 Promise 的狀態置為已失敗(Rejected),這時 then 方法執行“失敗”情況的回調(then 方法的第二參數)
function rebuy(){ console.log("開始買筆"); var p = new Promise(function(resolve,reject){ setTimeout(function(){ console.log("買筆失敗"); reject("沒帶夠錢"); },1000); }); return p; } function rework(data){ console.log("開始寫作業:"+data); var p = new Promise(function(resolve,reject){ setTimeout(function(){ console.log("寫完作業"); resolve("作業本"); },1000); }); return p; } rebuy().then(rework,function(data){ console.log(data); });
catch()方法:
- 它可以和 then 的第二個參數一樣,用來指定 reject 的回調
function rebuy(){ console.log("開始買筆"); var p = new Promise(function(resolve,reject){ setTimeout(function(){ console.log("買筆失敗"); reject("沒帶夠錢"); },1000); }); return p; } function rework(data){ console.log("開始寫作業:"+data); var p = new Promise(function(resolve,reject){ setTimeout(function(){ console.log("寫完作業"); resolve("作業本"); },1000); }); return p; } rebuy().then(rework).catch(function(data){ console.log(data); });
- 它的另一個作用是,當執行 resolve 的回調(也就是上面 then 中的第一個參數)時,如果拋出異常了(代碼出錯了),那么也不會報錯卡死 js,而是會進到這個 catch 方法中。
function buy(){ console.log("開始買筆"); var p = new Promise(function(resolve,reject){ setTimeout(function(){ console.log("買了筆芯"); resolve("數學作業"); },1000); }); return p; } function work(data){ console.log("開始寫作業:"+data); var p = new Promise(function(resolve,reject){ setTimeout(function(){ console.log("寫完作業"); resolve("作業本"); },1000); }); return p; } buy().then(function(data){ throw new Error("買了壞的筆芯"); work(data); }).catch(function(data){ console.log(data); });
all()方法:
Promise 的 all 方法提供了並行執行異步操作的能力,並且在所有異步操作執行完后才執行回調。
比如下面代碼,兩個個異步操作是並行執行的,等到它們都執行完后才會進到 then 里面。同時 all 會把所有異步操作的結果放進一個數組中傳給 then。
//買作業本 function cutUp(){ console.log('挑作業本'); var p = new Promise(function(resolve, reject){ //做一些異步操作 setTimeout(function(){ console.log('挑好購買作業本'); resolve('新的作業本'); }, 1000); }); return p; } //買筆 function boil(){ console.log('挑筆芯'); var p = new Promise(function(resolve, reject){ //做一些異步操作 setTimeout(function(){ console.log('挑好購買筆芯'); resolve('新的筆芯'); }, 1000); }); return p; } Promise.all([cutUp(),boil()]).then(function(results){ console.log("寫作業的工具都買好了"); console.log(results); });
race()方法:
race 按字面解釋,就是賽跑的意思。race 的用法與 all 一樣,只不過 all 是等所有異步操作都執行完畢后才執行 then 回調。而 race 的話只要有一個異步操作執行完畢,就立刻執行 then 回調。
注意:其它沒有執行完畢的異步操作仍然會繼續執行,而不是停止。
這里我們將上面樣例的 all 改成 race
Promise.race([cutUp(), boil()]).then(function(results){ console.log("哈哈,我先買好啦"); console.log(results); });
race 使用場景很多。比如我們可以用 race 給某個異步請求設置超時時間,並且在超時后執行相應的操作。
請求某個圖片資源
function requestImg(){ var p = new Promise(function(resolve, reject){ var img = new Image(); img.onload = function(){ resolve(img); } img.src = 'xxxxxx'; }); return p; } //延時函數,用於給請求計時 function timeout(){ var p = new Promise(function(resolve, reject){ setTimeout(function(){ reject('圖片請求超時'); }, 5000); }); return p; } Promise.race([requestImg(), timeout()]).then(function(results){ console.log(results); }).catch(function(reason){ console.log(reason); }); //上面代碼 requestImg 函數異步請求一張圖片,timeout 函數是一個延時 5 秒的異步操作。我們將它們一起放在 race 中賽跑。 //如果 5 秒內圖片請求成功那么便進入 then 方法,執行正常的流程。 //如果 5 秒鍾圖片還未成功返回,那么則進入 catch,報“圖片請求超時”的信息。
在工作中的應用
- 傳統回調模式
/*** 第一步:找到北京的id 第二步:根據北京的id -> 找到北京公司的id 第三步:根據北京公司的id -> 找到北京公司的詳情 目的:模擬鏈式調用、回調地獄 ***/ // 回調地獄 // 請求第一個API: 地址在北京的公司的id $.ajax({ url: 'https://www.easy-mock.com/mock/5a52256ad408383e0e3868d7/lagou/city', success (resCity) { let findCityId = resCity.filter(item => { if (item.id == 'c1') { return item } })[0].id $.ajax({ // 請求第二個API: 根據上一個返回的在北京公司的id “findCityId”,找到北京公司的第一家公司的id url: 'https://www.easy-mock.com/mock/5a52256ad408383e0e3868d7/lagou/position-list', success (resPosition) { let findPostionId = resPosition.filter(item => { if(item.cityId == findCityId) { return item } })[0].id // 請求第三個API: 根據上一個API的id(findPostionId)找到具體公司,然后返回公司詳情 $.ajax({ url: 'https://www.easy-mock.com/mock/5a52256ad408383e0e3868d7/lagou/company', success (resCom) { let comInfo = resCom.filter(item => { if (findPostionId == item.id) { return item } })[0] console.log(comInfo) } }) } }) } })
promise模式
// Promise 寫法 // 第一步:獲取城市列表 const cityList = new Promise((resolve, reject) => { $.ajax({ url: 'https://www.easy-mock.com/mock/5a52256ad408383e0e3868d7/lagou/city', success (res) { resolve(res) } }) }) // 第二步:找到城市是北京的id cityList.then(res => { let findCityId = res.filter(item => { if (item.id == 'c1') { return item } })[0].id findCompanyId().then(res => { // 第三步(2):根據北京的id -> 找到北京公司的id let findPostionId = res.filter(item => { if(item.cityId == findCityId) { return item } })[0].id // 第四步(2):傳入公司的id companyInfo(findPostionId) }) }) // 第三步(1):根據北京的id -> 找到北京公司的id function findCompanyId () { let aaa = new Promise((resolve, reject) => { $.ajax({ url: 'https://www.easy-mock.com/mock/5a52256ad408383e0e3868d7/lagou/position-list', success (res) { resolve(res) } }) }) return aaa } // 第四步:根據上一個API的id(findPostionId)找到具體公司,然后返回公司詳情 function companyInfo (id) { let companyList = new Promise((resolve, reject) => { $.ajax({ url: 'https://www.easy-mock.com/mock/5a52256ad408383e0e3868d7/lagou/company', success (res) { let comInfo = res.filter(item => { if (id == item.id) { return item } })[0] console.log(comInfo) } }) }) }