非同步解決方案
在js中任務的執行模型有兩種:同步模式
和非同步模式
。
-
同步模式:後一個任務B等待前一個任務A結束後,再執行。任務的執行順序和任務的排序順序是一致的。
-
非同步模式:每一個任務有一個或多個回撥函式,前一個任務A結束後,不是執行後一個任務B,而是執行任務A的回撥函式。而後一個任務B是不等任務A結束就執行。任務的執行順序,與任務的排序順序不一致。
非同步在實現上,依賴一些特殊的語法規則。從整體上來說,非同步方案經歷瞭如下的四個進化階段:回撥函式 —> Promise —> Generator —> async/await。其中 Promise、Generator 和 async/await 都是在 ES2015 之後,慢慢發展起來的、具有一定顛覆性的新非同步方案。
“回撥函式” 時期
事件監聽:任務的執行順序與程式碼的編寫順序無關,只與點選事件有沒有被觸發有關
釋出訂閱:任務執行的時機和某一事件的發生緊密關聯了起來。
回撥函式:回撥地獄導致可讀性和可維護性被破壞
Promise
說說你理解的 Promise(三種狀態,兩個過程)
Promise 物件是一個代理物件。它接受你傳入的 executor(執行器)作為入參,允許你把非同步任務的成功和失敗分別繫結到對應的處理方法上去。一個 Promise 例項有三種狀態:
• pending 狀態,表示進行中。這是 Promise 例項建立後的一個初始態;
• fulfilled(resolved) 狀態
• rejected 狀態,表示操作失敗、被拒絕。這是我們在執行器中呼叫 reject後,達成的狀態;
Promise例項的狀態是可以改變的,但它只允許被改變一次。當我們的例項狀態從 pending 切換為 rejected 後,就無法再扭轉為 fulfilled,反之同理。當 Promise 的狀態為 resolved 時,會觸發其對應的 then 方法入參裡的 onfulfilled 函式;當 Promise 的狀態為 rejected 時,會觸發其對應的 then 方法入參裡的 onrejected 函式。
Promise 常見方法有哪些?各自是幹嘛的?
Promise的方法有以下幾種:
• Promise.all(iterable):這個方法返回一個新的 promise 物件,該 promise 物件在 iterable 引數物件裡所有的 promise 物件都成功的時候才會觸發成功,一旦有任何一個 iterable 裡面的 promise 物件失敗則立即觸發該 promise 物件的失敗。
var p1 = Promise.resolve('1號選手'); var p2 = '2號選手'; var p3 = new Promise((resolve, reject) => { setTimeout(resolve, 100, "3號選手"); }); Promise.all([p1, p2, p3]).then(values => { console.log(values); // ["1號選手", "2號選手", "3號選手"] });
使用場景:執行某個操作需要依賴多個介面請求回的資料,且這些介面之間不存在互相依賴的關係。這時使用Promise.all()
,等到所有介面都請求成功了,它才會進行操作。
let promise1 = new Promise((resolve) => { setTimeout(() => { resolve('promise1操作成功'); console.log('1') }, 3000); }); let promise2 = new Promise((resolve) => { setTimeout(() => { resolve('promise1操作成功'); console.log('2') }, 1000); }); Promise.all([promise1, promise2]).then((result) => { console.log(result); });
• Promise.race(iterable):當 iterable 引數裡的任意一個子 promise 被成功或失敗後,父 promise 馬上也會用子 promise 的成功返回值或失敗詳情作為引數呼叫父 promise 繫結的相應處理函式,並返回該 promise 物件。
var p1 = new Promise(function(resolve, reject) { setTimeout(resolve, 100, "1號選手"); }); var p2 = new Promise(function(resolve, reject) { setTimeout(resolve, 50, "2號選手"); }); // 這裡因為 2 號選手返回得更早,所以返回值以 2號選手為準 Promise.race([p1, p2]).then(function(value) { console.log(value); // "2號選手" });
let promise1 = new Promise((resolve) => { setTimeout(() => { resolve('promise1操作成功'); console.log('1') }, 3000); }); let promise2 = new Promise((resolve, reject) => { setTimeout(() => { reject('promise1操作失敗'); console.log('2') }, 1000); }); Promise.race([promise1, promise2]) .then((result) => { console.log(result); }) .catch((error) => { console.log(error); })
1s後,promise2進入rejected狀態,由於一個例項的狀態發生了變化,所以Promise.race()就立刻執行了,其他例項中再發生變化,它也不管了。
- Promise.reject(reason): 返回一個狀態為失敗的Promise物件,並將給定的失敗資訊傳遞給對應的處理方法
- Promise.resolve(value):它返回一個 Promise 物件,但是這個物件的狀態由你傳入的value決定,情形分以下兩種:
- 如果傳入的是一個帶有 then 方法的物件(我們稱為 thenable 物件),返回的Promise物件的最終狀態由then方法執行決定
- 否則的話,返回的 Promise 物件狀態為 fulfilled,同時這裡的 value 會作為 then 方法中指定的 onfulfilled 的入參
真題分析
Promise 中的處理函式是非同步任務
const promise = new Promise((resolve,reject) =>{ console.log(1); resolve(); console.log(2); }) promise.then(()=>{ console.log(3); }) console.log(4);
then 方法中傳入的任務是一個非同步任務。resolve() 這個呼叫,作用是將 Promise 的狀態從 pending 置為 fulfilled,這個新狀態會讓 Promise 知道“我的 then 方法中那個任務可以執行了”——注意是”可以執行了“,而不是說”立刻就會執行“。畢竟作為一個非同步任務,它的基本修養就是要等同步程式碼執行完之後再執行。所以說數字 3 的輸出排在最後。
Promise 物件的狀態只能被改變一次
const promise = new Promise((resolve,reject) =>{ resolve('第1次resolve') console.log('resolve後的普通邏輯1'); reject('error') resolve('第2次resolve'); console.log('resolve後的普通邏輯2'); }) promise.then((res)=>{ console.log('then:',res); }).catch((err)=>{ console.log('catch',err); })
這段程式碼裡,promise 初始狀態為 pending,我們在函式體第一行就用 resolve 把它置為了 fulfilled 態。這個切換完成後,後續所有嘗試進一步作狀態切換的動作全部不生效,所以後續的 reject、resolve大家直接忽略掉就好;需要注意的是,我們忽略的是第一次 resolve 後的 reject、resolve,而不是忽略它身後的所有程式碼。因此 console.log(‘resolve後的普通邏輯’) 這句,仍然可以正常被執行。至於這裡為啥它輸出在 ”then: 第 1 次 resolve“ 的前面,原因和上一題是一樣一樣的~
Promise 值穿透
Promise.resolve(1) .then(Promise.resolve(2)) .then(3) .then() .then(console.log)
then 方法裡允許我們傳入兩個引數:onFulfilled
(成功態的處理函式)和onRejected
(失敗態的處理函式)。
你可以兩者都傳,也可以只傳前者或者後者。但是無論如何,then 方法的入參只能是函式。萬一你想塞給它一些亂七八糟的東西,它就會“翻臉不認人”。
具體到我們這個題裡,第一個 then 方法中傳入的是一個 Promise 物件,then 說:”我不認識“;第二個 then 中傳入的是一個數字, then 繼續說”我不認識“;第四個乾脆啥也沒穿,then 說”入參undefined了,拜拜“;直到第五個入參,一個函式被傳了進來,then 哭了:”終於等到一個我能處理的!“,於是只有最後一個入參生效了。
在這個過程中,我們最初 resolve 出來那個值,穿越了一個又一個無效的 then 呼叫,就好像是這些 then 呼叫都是透明的、不存在的一樣,因此這種情形我們也形象地稱它是 Promise 的“值穿透”。
例項
現在有三個請求,請求A、請求B、請求C。請求C要將請求B的請求回來的資料做為引數,請求B要將請求A的請求回來的資料做為引數。
回撥寫法
$.ajax({ success:function(res1){ //------請求B 開始---- $.ajax({ success:function(res2){ //----請求C 開始--- $.ajax({ success:function(res3){ } }); //---請求C 結束--- } }); //------請求B 結束----- } }); //------請求A 結束---------
promise寫法
let promise = new Promise((resolve, reject) => { if (true) { //呼叫操作成功方法 resolve('操作成功'); } else { //呼叫操作異常方法 reject('操作異常'); } }); //then處理操作成功,catch處理操作異常 promise.then(requestA) .then(requestB) .then(requestC) .catch(requestError); function requestA() { console.log('請求A成功'); return '下一個是請求B'; } function requestB(res) { console.log('上一步的結果:' + res); console.log('請求B成功'); return '下一個是請求C'; } function requestC(res) { console.log('上一步的結果:' + res); console.log('請求C成功'); } function requestError() { console.log('請求失敗'); }