js異步從入門到放棄(實踐篇) — 常見寫法&面試題解析

前文

該系列下的前幾篇文章分別對不同的幾種異步方案原理進行解析,本文將介紹一些實際場景和一些常見的面試題。(積累不太夠,後面想到再補)

正文

流程調度(schedule)

流程調度,最常見的就是繼發併發(或者說串行和並行)兩種類型,在日常工作裏都很常見。接下來結合實際場景進行說明:

1. 串行執行一系列異步操作,每一步依賴前一步的結果

串行執行的關鍵是,將每一個異步任務放到前一個異步任務的回調函數裏執行

  • 場景:一串連續的動畫,每個動畫必須等待前一個動畫完全執行完,並且如果某個動畫執行失敗,則不繼續執行下一個動畫。
  • 代碼:
    
    // 這裏假定一共要執行5個動畫 
    
    // getAnimation 函數模擬執行動畫 接收參數i表述動畫編號 返回一個promose
    const getAnimation = (i) => new Promise((resolve, reject) => {
        setTimeout(()=>{
            // 隨機返回true或者false 
            const isSuccess = Math.random() > 0.5
            console.log(`第${i}個動畫執行`)
            if(isSuccess){
                return resolve(isSuccess)
            }
            return reject(isSuccess)
        },1000)
    })

    // 1.promise實現 核心就是嵌套代碼
    const serialScheduleByPromise = () => {
        let p = Promise.resolve(true)
        const tasks = []
        for(let i=0;i < 5; i++){
            p = p.then(isSuccess=>{
                if(isSuccess){
                    return getAnimation(i+1)
                }
            }).catch((err)=>{
            return console.log(`執行失敗`)
        })
        }
    }
    serialScheduleByPromise()

    // 2.async/await實現
    const serialScheduleByAsync = async () => {
        try{
        for(let i=0;i < 5; i++){
            await getAnimation(i+1)
        }}catch(e){
            console.log(`執行失敗`)
        }
    }
    serialScheduleByAsync()

async/await的語法雖然沒有單獨解析,但是本質就是前一篇介紹的帶自動執行器的generator而已,因此不再贅述
可以看到,async的寫法代碼更簡潔,而且邏輯更清晰,可讀性更強。

2. 並行執行所有異步操作,等到所有請求完成後,按照讀取請求的順序輸出結果

場景:併發讀取5個數據(爲了方便 分別編號爲1-5),然後按照實際讀取順序結果

    const getDataById = (i) => new Promise((resolve, reject) => {
        // 隨機延遲一個時間返回結果,
        const delay = Math.floor(Math.random() * Math.floor(3000)) // 延遲時間可能爲 0,1000,2000 毫秒
        setTimeout(()=>{
            return resolve(i)
        }, delay)
    })
    
    // 1.promise實現
    const concurrentScheduleByPromise = ()=>{
        const promises = []
        const result = []
        for(let i = 0;i < 5;i++){
            promises[i] = getDataById(i+1)
            promises[i].then(i=>{
                result.push(i)
            })
        }
        Promise.all(promises).then(()=>{
            result.forEach(id=>{
                console.log(id)
            })
        })
    }
    concurrentScheduleByPromise()

    // async/await實現
    const concurrentScheduleByAsync = () => {
        for(let i = 0 ;i < 5; i++){
            let task = async function (){
                console.log(await getDataById(i+1))
            }
            task()
        }
    }
    concurrentScheduleByAsync()

注意辨析這裏concurrentScheduleByAsyncserialScheduleByAsync的區別,關鍵點是同一個async函數內部的await纔是按順序執行

流程調度裏比較常見的一種錯誤是“看似串行”的寫法,可以感受一下這個例子:

    const getPromise = (name) =>new Promise(resolve=>{
        setTimeout(()=>{
            console.log(name)
            resolve(name)
        },1000)
    })
    
    // 判斷以下幾種寫法的輸出結果 
    Promise.resolve().then(getPromise('1a')).then(getPromise('1b')).then(getPromise('1c')) 
    Promise.resolve().then(()=>getPromise('2a')).then(()=>getPromise('2b')).then(()=>getPromise('2c')) 
    Promise.resolve().then(getPromise('3a').then(getPromise('3b').then(getPromise('3c')))) 
    Promise.resolve().then(()=>getPromise('4a').then(()=>getPromise('4b').then(()=>getPromise('4c')))) 

辨別輸出順序

這類題目一般出現在面試題裏。

1. 基礎-區分不同任務類型

    console.log(1)
    new Promise(resolve => {
        console.log(2)
        setTimeout(() => {
            console.log(10)
        }, 10)
        resolve()
        console.log(3)
    }).then(() => {
        console.log(5)
    })

    setTimeout(() => {
        console.log(7)
        Promise.resolve().then(() => {
            console.log(9)
        })
        console.log(8)
    })

    Promise.resolve().then(() => {
        console.log(6)
    })
    console.log(4)
    // 輸出 1 2 3 4 5 6 7 8 9 10

2. 複雜-加入瀏覽器render

<style>
    .outer {
        padding: 30px;
        background-color: aqua;

    }

    .inner {
        height: 100px;
        background-color: brown;
    }
</style>

<body>
    <div class="outer">outer
         <div class="inner">inner</div> 
    </div>
</body>
<script>
    var outer = document.querySelector('.outer');
    var inner = document.querySelector('.inner');

    // Let's listen for attribute changes on the
    // outer element
    new MutationObserver(function () {
        console.log('mutate');
    }).observe(outer, {
        attributes: true
    });

    // Here's a click listener…
    function onClick() {
        console.log('click');

        setTimeout(function () {
            console.log('timeout');
        }, 0);

        Promise.resolve().then(function () {
            console.log('promise');
        });

        outer.setAttribute('data-random', Math.random());
    }

    // …which we'll attach to both elements
    inner.addEventListener('click', onClick);
    outer.addEventListener('click', onClick);
    // innner.click() 試試直接點擊和js執行click的區別
</script>

這類問題實質上就是辨析異步任務隊列類型,詳細內容和解析可以直接看js異步從入門到放棄(三)- 異步任務隊列(task queues)

小結

這篇文章主要是給這個系列做個簡單的收尾,單獨純異步的問題難點其實也不多,偷個懶,後面想到了再補上。


如果覺得寫得不好/有錯誤/表述不明確,都歡迎指出
如果有幫助,歡迎點贊和收藏,轉載請徵得同意後著明出處。如果有問題也歡迎私信交流,主頁有郵箱地址
如果覺得作者很辛苦,也歡迎打賞~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章