1. 程式人生 > 實用技巧 >非同步解決方案

非同步解決方案

在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) 狀態

,表示成功完成。這是我們在執行器中呼叫 resolve 後,達成的狀態;
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('請求失敗');
    }