Promise 解析和事件循環機制

js單線程(線程中擁有唯一的一個事件循環)

  • js分為同步任務和異步任務,同步任務都是在主線程上執行。當一個任務執行完畢后,執行后一個任務,形成一個執行棧
  • 主線程之外,事件觸發線程管著一個任務隊列,異步任務會被主線程掛起,不會進入主線程,而是進入任務隊列。只要異步任務有了運行結果,就會在隊列任務中放置一個事件;
  • 一旦執行棧中所有的同步任務執行完畢后,系統就會讀取任務隊列,將可運行的異步任務添加到可執行棧中,開始執行。

為什么js是單線程的?

JS的主要用途就是與用戶交互,操作DOM,假如JS同時有兩個線程,一個線程中在某個DOM節點上添加內容,另一個線程需要執行刪除該節點操作,就會產生沖突。

事件循環機制告訴我們JavaScript的執行順序。

單線程意味著所有任務都需要排隊,前一任務結束,才會執行后一個任務,如果前一個任務耗時很長,后一個任務就不得不一直等著。
JS引擎執行異步代碼不用等待,是因為有事件隊列和事件循環。

事件循環是指主線程重復從事件隊列中取消息、執行的過程。指整個執行流程。

事件隊列是一個存儲著待執行任務的序列,其中的任務嚴格按照時間先后順序執行,排在隊頭的任務會率先執行,而排在隊尾的任務會最后執行。

事件隊列:

一個線程中,事件循環是唯一的,但是任務隊列可以有多個;
任務隊列又分macro-task(宏任務)和micro-task(微任務);
macro-task包括:script(整體代碼)、setTimeout、setInterval、setImmediate、I/O、UI rendering;
micro-task包括:process.nextTick, Promise, Object.observe(已廢棄), MutationObserver(html5新特性)
setTimeout/Promise等稱為任務源,而進入任務隊列的是他們制定的具體執行任務;來自不同任務源的任務會進入到不同的任務隊列,其中setTimeout與setInterval是同源的;
宏任務可以理解成每次執行棧執行的代碼就是一個宏任務。

事件運行機制

(1)執行一個宏任務(棧中沒有就從事件隊列中獲取)

(2)執行過程中如果遇到微任務,就將它添加到微任務的任務隊列中;

(3)宏任務執行完畢后,立即執行當前微任務隊列的所有微任務;

(4)當前微任務執行完畢,開始檢查渲染,然后GUI線程接管渲染;

(5)渲染完畢后,JS線程繼續接管,開始下一個宏任務

代碼實例

    async function async1() {           
        console.log("async1 start");  //(2)        
        await  async2();            
        console.log("async1 end");   //(6)    
    }        
    async  function async2() {          
        console.log( 'async2');   //(3)     
    }       
    console.log("script start");  //(1)      
    setTimeout(function () {            
        console.log("settimeout");  //(8)      
    },0);        
    async1();        
    new Promise(function (resolve) {           
        console.log("promise1");   //(4)         
        resolve();        
    }).then(function () {            
        console.log("promise2");    //(7)    
    });        
    console.log('script end');//(5)

流程解析
先按順序執行同步代碼 從‘script start‘開始,

執行到setTimeout函數時,將其回調函數加入隊列(此隊列與promise隊列不是同一個隊列,執行的優先級低于promise。

然后調用async1()方法,await async2();//執行這一句后,輸出async2后,await會讓出當前線程,將后面的代碼加到任務隊列中,然后繼續執行test()函數后面的同步代碼

繼續執行創建promise對象里面的代碼屬于同步代碼,promise的異步性體現在then與catch處,所以promise1被輸出,然后將then函數的代碼加入隊列,繼續執行同步代碼,輸出script end。至此同步代碼執行完畢。

開始從隊列中調取任務執行,由于剛剛提到過,setTimeout的任務隊列優先級低于promise隊列,所以首先執行promise隊列的第一個任務,因為在async函數中有await表達式,會使async函數暫停執行,等待表達式中的 Promise 解析完成后繼續執行 async 函數并返回解決結果。

所以先執行then方法的部分,輸出promise2,然后執行async1中await后面的代碼,輸出async1 end。。最后promise隊列中任務執行完畢,再執行setTimeout的任務隊列,輸出settimeout。

setTimeout(fn,0)的含義是指某個任務在主線程最早可得的空閑時間執行。它在“任務隊列”的尾部添加一個事件,因此要等到同步任務和“任務隊列”現有的時間處理完才會得到執行。

按照事件循環機制分析以上代碼運行流程

1. 首先,事件循環從宏任務(macrotask)隊列開始,這個時候,宏任務隊列中,只有一個script(整體代碼)任務;當遇到任務源(task source)時,則會先分發任務到對應的任務隊列中去。

2. 然后我們看到首先定義了兩個async函數,接著往下看,然后遇到了 console 語句,直接輸出 script start。輸出之后,script 任務繼續往下執行,遇到 setTimeout,其作為一個宏任務源,則會先將其任務分發到對應的隊列中。

3. script 任務繼續往下執行,執行了async1()函數,前面講過async函數中在await之前的代碼是立即執行的,所以會立即輸出async1 start
遇到了await時,會將await后面的表達式執行一遍,所以就緊接著輸出async2,然后將await后面的代碼也就是console.log('async1 end')加入到microtask中的Promise隊列中,接著跳出async1函數來執行后面的代碼。

4. script任務繼續往下執行,遇到Promise實例。由于Promise中的函數是立即執行的,而后續的 .then 則會被分發到 microtask 的 Promise 隊列中去。所以會先輸出 promise1,然后執行 resolve,將 promise2 分配到對應隊列。

5. script任務繼續往下執行,最后只有一句輸出了 script end,至此,全局任務就執行完畢了。
根據上述,每次執行完一個宏任務之后,會去檢查是否存在 Microtasks;如果有,則執行 Microtasks 直至清空 Microtask Queue。
因而在script任務執行完畢之后,開始查找清空微任務隊列。此時,微任務中, Promise 隊列有的兩個任務async1 endpromise2,因此按先后順序輸出 async1 end,promise2。當所有的 Microtasks 執行完畢之后,表示第一輪的循環就結束了。

6. 第二輪循環依舊從宏任務隊列開始。此時宏任務中只有一個 setTimeout,取出直接輸出即可,至此整個流程結束。

事件隊列流程圖

異步隊列任務.png

promise對象實現

/**
 * 異步解決:
 * 此時我們使用一個發布訂閱者模式,在pending狀態的時候將成功的函數和失敗的函數存到各自的回調隊列數組中,等一旦reject或者resolve,就調用它們:
 * 在pending態的時候將所有的要在成功態執行的方法都存到onResolveCallbacks數組中
 * 
    鏈式解決: 遞歸處理
    根據原生promise的then的用法,我們總結一下:
    1.then方法如果返回一個普通的值,我們就將這個普通值傳遞給下一個then
    2.then方法如果返回一個promise對象,我們就將這個promise對象執行結果返回到下一個then
    普通的值傳遞很好辦,我們將第一次then的onFulfilled函數返回的值存到x變量里面,在然后resolve出去就可以了,
    復雜的是then里面返回的是一個promise的時候怎么辦,因為返回的promise的我們要判斷他執行的狀態,來決定是走成功態,還是失敗態,
    這時候我們就要寫一個判斷的函數resolvePromise(promise2, x, resolve, reject)來完成這個判斷
    這個方法的主要作用是用來判斷x的值,如果x的值是一個普通的值,就直接返回x的值,如果x的值是一個promise,就要返回x.then() 執行的結果,核心代碼如下


 */
const judgePromise = (p2, x, resolve, reject) => {
    if (p2 === x) {
        return reject(
            new TypeError("傳值有誤")
        )
    }
    // 是否為promise對象
    if (typeof x === "object" && x != null || typeof x === "function") {
        try {
            let then = x.then; //去對象的then函數
            if (typeof then === "function") {
                then.call(x, data => {
                    resolve(data);
                }, err => {
                    reject(err);
                })
            } else {
                resolve(x);
            }
        } catch (err) {
            reject(err);
        }


    } else {
        resolve(x);
    }
}
class Mypromise {
    constructor(executor) {
        this.state = "pending"; //狀態值
        this.value = undefined; //成功返回值
        this.reason = undefined; //失敗返回值
        this.onResolvedCallbacks = []; // 如果成功的回調函數數組
        this.onRejectedCallbacks = []; // 如果失敗的回調函數數組
        //成功的
        let resolve = value => {
                // pending用來屏蔽的,resolve和reject只能調用一個,不能同時調用,這就是pending的作用
                if (this.state == "pending") {
                    this.state = "fullFilled";
                    this.value = value;
                    // 發布成功的事件,執行事件(訂閱發布模式--發布過程)
                    this.onResolvedCallbacks.forEach(fn => fn());
                }
            }
            //失敗
        let reject = reason => {
            if (this.state == "pending") {
                this.state = "rejected";
                this.reason = reason;
                this.onRejectedCallbacks.forEach(fn => fn());
            }
        }
        try {
            executor(resolve, reject);
        } catch (err) {
            reject(err);
        }
    }
    then(onFullFilled, onRejected) {
        // 既然遞歸解決 第一個回調函數返回一個promise對象,以此類推...
        let p2 = new Mypromise((resolve, reject) => {
            // 同步的(理解觸發時的下一刻1毫秒立馬有反應,狀態立馬變化)
            if (this.state == "fullFilled") {
                setTimeout(() => {
                        try {
                            let x = onFullFilled(this.value);
                            judgePromise(p2, x, resolve, reject);

                        } catch (err) {
                            reject(err); //只要報錯reject
                        }
                    }, 0) //同步無法使用p2,所以借用setiTimeout異步的方式

            }
            if (this.state == "rejected") {
                setTimeout(() => {
                        try {
                            let x = onFullFilled(this.reason);
                            judgePromise(p2, x, resolve, reject);

                        } catch (err) {
                            reject(err); //只要報錯reject
                        }
                    }, 0) //同步無法使用p2,所以借用setiTimeout異步的方式
            }
            // 異步(理解觸發時的下一刻1毫秒后沒有反應,狀態沒有變化)
            if (this.state == "pending") {
                // 在pengding狀態時,先把訂閱者的事件存到數組里(訂閱發布模式--訂閱過程)
                this.onResolvedCallbacks.push(() => {
                    setTimeout(() => {
                            try {
                                let x = onFullFilled(this.value);
                                judgePromise(p2, x, resolve, reject);

                            } catch (err) {
                                reject(err); //只要報錯reject
                            }
                        }, 0) //同步無法使用p2,所以借用setiTimeout異步的方式
                })
                this.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                            try {
                                let x = onFullFilled(this.reason);
                                judgePromise(p2, x, resolve, reject);

                            } catch (err) {
                                reject(err); //只要報錯reject
                            }
                        }, 0) //同步無法使用p2,所以借用setiTimeout異步的方式
                })
            }

        })
        return p2;
    }
}
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。