es6 異步處理之 Promise學習總結

    =========================事件循環==================
    先看以下的例子:
    例1:
    console.log("a");

    setTimeout(() => {
        console.log("b");
    }, 0)

    console.log("c");

    // 以上的結果是 先輸出 a, c, b


    // 然後看下面的例子:
    console.log("a");

    setTimeout(() => {
        console.log("b");
    }, 0)

    for(let i = 0; i < 1000; i ++){
        console.log("c")
    }

    // 以上的結果是 a 1000個c  最後是 b

    問:爲什麼總都是b在最後呢?


    事件回顧:
    JS運行的環境稱之爲宿主環境。

    執行棧:call stack , 一個數據結構, 用於存放各種函數的執行環境,每一個函數執行之前,
    它的相關信息會加入到執行棧。 函數調用之前, 創建執行環境, 然後加入到執行棧; 函數調用之後,銷燬執行環境

    JS引擎執行的都是棧的最頂部,執行完後,頂部的執行上下文會銷燬(出棧),
    調用函數之前一定要先入棧(創建一個對應的上下文),執行完後出棧,銷燬對應的上下文
    
    異步函數: 某些函數不會立即執行,需要等到某個時機到達後纔會執行,這樣的函數稱爲
    異步函數,比如:事件處理函數,setTimout,setTimeItervel等。異步函數的執行時機,會被宿主環境控制。

    瀏覽器宿主環境中包含5個線程:
    1. JS引擎: 負責執行棧的最頂部代碼
    2. GUI線程: 負責渲染頁面
    3. 事件監聽線程: 負責監聽各種事件
    4. 計時線程: 負責計時, 如setTimeOut, setTimeInterval等
    5. 網絡線程: 負責網絡通信, 如 ajax, axios等

    當上面的線程發生某些事情,如果該線程發現,這件事情有處理程序,他會將該處理程序加入
    到一個叫做事件隊列的內存中。 當JS引擎發現,執行棧中已經沒有了任何內容後,會將事件隊列中
    的第一個函數加入到執行棧中執行。

    JS引擎對事件隊列的取出方式,以及與宿主環境的配合,稱之爲事件循環。

    事件隊列在不同的宿主環境中有所差異,大部分宿主環境會將事件隊列進行細分。
    在瀏覽器中,事件隊列分爲兩種:
    
    宏隊列: macroTask, 計時器結束的回調,事件回調,http回調等絕大部分異步函數進入宏隊列
    微隊列: mutationObserver, Promise產生的回調進入微隊列
    mutationObserver: 用於監聽dom裏面屬性或者結構發生變化時,dom發生變化。

    當執行棧清空時, JS引擎首先會將微隊列中的所有任務依次執行結束,如果沒有微隊列的任務,則執行宏隊列裏面的任務

   
    =================es6異步處理 Promise=================
    事件和回調函數的缺陷:
    我們習慣於使用傳統的回調或事件處理來解決回調
    事件: 某個對象的屬性是一個函數,當發生某一事件時,運行該函數
    dom.onclick = function(){}

    回調: 運行某個函數以實現某個功能的時候,傳入一個函數作爲參數,當發生某件事的時候,會運行該函數
    dom.addEventLinster("click",function(){})


    本質上,事件和回調並沒有本質的區別,只是函數放置的位置不同而且。

    該模式主要面臨以下問題:
    1. 回調地獄:某個異步操作需要等待之前的異步操作完成,無論用回調還是事件,都會陷入不斷的嵌套
    2. 異步之間的聯繫: 某個異步操作要等待多個異步操作的結果,對這種聯繫的處理,會讓代碼的複雜度劇增

    異步處理的通用模型
    ES 官方參考了大量的異步場景,總結一套異步的通過模型,該模型可以覆蓋幾乎所有的異步場景,甚至同步場景

    值得注意的是,爲了兼容舊系統,ES6 並不打算拋棄過去的做法,只是基於該模型推出的一個權限的 API, 使用該APi, 讓異步處理更加的簡潔優雅
    理解該API, 最重要的是,理解他的api
    1. ES6 將某一件可能發生異步操作的事情,可以分爲兩個階段: unsettled 和 settle
    unsettled: 未決階段,表示事情還在進行前提的處理,並沒有發生通向結果的那件事;
    settled: 已決階段, 事情已經有了一個結果,不管這個結果是好是壞,整件事情無法逆轉;

    事情總是從 未決階段 逐步發展到 已決階段的。 並且,未決階段擁有控制何時通向已決階段的能力
    
   2. ES6 將事情劃分爲三種狀態: pending, resolved rejected
    pending: 掛起,處於未決階段,則表示這件事情還在掛起(最終的結果還沒有出來)
    resolved: 已處理, 已決階段的一種狀態,表示整件事情出現了結果,並且是正常邏輯進行下去的結果
    rejected: 已拒絕, 已決階段的一種狀態,表示整件事情出現結果,並不是一個正常的結果,錯誤的結果

   既然未決階段有權決定事情的走向,因此,未決階段可以決定事情最終的狀態!
   
   我們 把事情變爲resolved狀態的過程叫做:resolve,推向該狀態時候,可能還會傳遞一些數據。
   我們 把事情變爲rejected狀態的過程叫做:rejected,推向該狀態時候,可能還會傳遞一些數據,一些錯誤的信息。

   無論是哪個階段,過程都是不可逆的

   3. 當事情的處理到達已決狀態, 不同的狀態決定不同的處理
      resolved狀態: 這是一個正常的已決的狀態,後續處理表示未thenable
      rejected狀態:這個一個非正常的已決的狀態,後續處理表示未catchable

      後續處理可能有多個,因此會形成作業隊列,這些後續處理按照順序,當狀態到達後依次執行

  Promise API:
  promise 不是消除回調,而是將回調用兩種狀態來返回
  使用方法:
    const pro = new Promise((resolve, reject) => {
        // 未決階段,也可以理解爲等待階段,異步之前做的事情,代碼在這裏寫
        // 通過調用resolve函數將Promise推向已決階段的resolve狀態(成功)
        // 通過調用reject函數將Promise推向已經階段的reject狀態(失敗)
        // resolve 和rejecte 均可以傳遞最多一個參數,表示推送狀態的數據
    })

    pro.then(data => {
        // 這是thenable 函數, 如果當前的Promise已經是resolved狀態,該函數會立即執行, 
        // 如果當前是爲決狀態,則會加入到作業隊列,等待到達resolved狀態後執行
        // data爲resolved 的狀態數據

    }, err => {
         // 這是catchable 函數, 如果當前的Promise已經是rejected狀態,該函數會立即執行
        // 如果當前是爲決狀態,則會加入到作業隊列,等待到達rejected狀態後執行
        // err 爲rejected 的狀態數據

    })

    細節:
    1. 未決階段的函數的代碼是同步代碼的,會立即執行
    2. thenable 和catchable 函數是異步的,就是放到立即執行,但是必須需要等到同步代碼執行完後,纔會執行,並且是加入的是微隊列裏面
    3. pro.then() 可以單獨thenable的函數, 也可單獨添加catchable的函數, 如 pro.catch();
    4.  在未決狀態中發生錯誤或者拋出錯誤,會將錯誤推向reject,並且會被catchable捕獲
    5. 一旦狀態推向已決,狀態不可以改變
    6. promise並沒有消除回調, 只是讓回調變的可控

    =======================Promise 串聯======================
    當後續的promise需要用到之前promise產生的結果;

    Promise 無論then方法,還是catch方法, 返回的是一個全新的Promise對象,狀態滿足下列規則:

     1. 返回的Promise對象如果是掛起狀態(未決),新的Promise的狀態也是掛起狀態
     2. 如果當前的Promise是已決狀態, 會運行後續的函數,並將後續處理函數的結果(返回值)
      作爲resolved狀態數據,會應用奧新的Promise中; 如果後續處理函數發生錯誤,則把返回值當作
      rejected狀態數據,應用到新的Promise中(後續的Promise,需要等到前面的promise已決)

      如果前面的Promise的後續處理,返回的是一個Promise, 那麼後面的Promise的狀態和前面的狀態信息保持一致

       const pro = new Promise((resolve, reject) => {
        resolve(1);
    });

    const pro1 = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(3);
        }, 3000)
    });

    pro.then(result => {
        console.log("第一個promise的狀態")
        console.log(result) // 1
        return pro1;
    }).then(result => {
        console.log(result) // 3 下面是undefined,是因爲這裏沒有返回
    }).then(result => {
        console.log(result) //undefined
    })

    // 輸出結果 3 和 undefined 在3秒後打印


    Promise的其他api

    原型成員(實例成員):
    then: 註冊一個後續處理函數,當Promise爲resolved狀態時運行該函數
    catch: 註冊一個後續處理函數, 當Promise爲rejected狀態時運行該函數
    finally[es2018] 註冊一個後續處理函數(無參),當Promise爲已決時運行該函數
    例如:
    const pro = new Promise((resolve, reject) => {
        resolve(1)
    });

    pro.finally(() => {
        console.log("第一次finally的執行");
    })
    pro.then(res => {
        console.log(res, "已決resolved得出的結果")
    })

    pro.catch(err => {
        console.log(err, "已決reject得出結果")
    })

    pro.finally(() => {
        console.log("已決第二次執行finally")
    })

    // 得出結果如下:
    // 第一次finally的執行
    // 1 已決resolved得出的結果
    // 已決第二次執行finally

    構造函數成員(靜態成員)
    resolve: 該方法返回一個resolved狀態的Promise,傳遞的數據作爲狀態數據;
    特殊情況:如果傳遞的數據是promise, 則直接返回傳遞的Promise對象
    例如:
    const pro = new Promise((resolve, reject) => {
        // 這裏面的代碼是同步代碼
        resolve(1);
    })

    //  等效於
    const pro = Promise.resolve(1);

    特殊情況:
    const pro = new Promise((resolve, reject) => {
        // 這裏面的代碼是同步代碼
        resolve(1);
    })
    const pro1 = Promise.resolve(pro);

    //  等效於
    const pro1 = pro;
    

    reject: 該方法返回一個rejectd狀態的Promise,傳遞的數據作爲狀態數據
    例如:
    const pro = new Promise((resolve, reject) => {
        // 這裏面的代碼是同步代碼
        reject(1);
    })

    //  等效於
    const pro = Promise.reject(1);

    all(iterable): 這個方法返回一個新的Promise對象, 該promise對象在所有promise數組中
    所有的promise都已決resolved的時候觸發成功方法, 一旦有任何一個promise裏面的已決rejected狀態
    一行,會把錯誤立即返回;
    例如:
      function getRandom(min, max) {
        return Math.floor(math.getRandom() * ((max - min) + min))
    }

    const proms = [];
    for (let i = 0; i < 10; i++) {
        proms.push(new Promise((resolve, rejecct) => {
            setTimeout(() => {
                resolve(i)
            }, getRandom(1000, 5000));
        }))
    }

    // 等待所有promise完成
    Promise.all(proms).then(res => {
        console.log("所有promise 已決resolved狀態後執行")
    })

    race: 有一個成功那就成功,有一個失敗那就失敗,返回的是一個Promise
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章