第7章 JS基礎-異步【三座大山之三,必考!!!】

返回章節目錄

目錄

1.同步和異步的區別

單線程和異步

爲什麼需要異步

2.應用場景

3.promise

4.練習題

1.手寫用promise加載一張圖片

2.setTimeout筆試題


 

1.同步和異步的區別

單線程和異步

1.JS是單線程語言,只能同時做一件事

2.瀏覽器和nodejs已支持JS啓動進程,比如Web Worker

3.JS和DOM渲染共用同一個線程,因爲JS可修改DOM結構

DOM渲染時,JS執行必須停止,JS執行時,DOM渲染也必須停止

爲什麼需要異步

遇到等待(網絡請求,定時任務)不能卡住,總不能去等待網絡請求然後瀏覽器卡在這裏吧????

等待的過程cpu也是空閒的,這樣也會浪費資源

異步回調都是用callback的函數形式

異步:

同步:

不點擊alert就會阻塞卡住,3不會打印

異步不會阻塞代碼執行,同步會阻塞代碼執行

 

2.應用場景

異步主要用在等待的情況(網絡請求、定時任務

先打印start,再異步執行網絡請求,接着打印end,網絡請求什麼時候回來就什麼時候打印data1的數據

先打印start,接着定義image,再加上onload事件,什麼時候加載完就什麼時候執行回調,這也是回調的另一種形式。

接着執行img.src賦值,這裏一執行就開始加載圖片,然後打印end,最後圖片加載完的時候執行回調打印loaded

 

3.promise

早期JS會陷入回調地獄(callback hell),獲取第一份數據之後再獲取第二份數據,獲取到第二份數據後再獲取第三份數據...這樣的嵌套回調太多了就特別亂,就像地獄一般

嵌套的回調地獄的問題導致了promise的出現,也就是說promise的出現,解決了callback嵌套的回調地獄(callback hell)問題

promise的then都是並列的管道形式,當然這裏也是回調,但是不是嵌套的更方便調試和查看

具體promise的例子我們後面在說,這裏看一下形式

 

4.練習題

1.手寫用promise加載一張圖片

function loadImg(src) {
    const p = new Promise(
        (resolve, reject) => {
            const img = document.createElement('img')
            img.onload = () => {
                resolve(img)
            }
            img.onerror = () => {
                const err = new Error(`圖片加載失敗 ${src}`)
                reject(err)
            }
            img.src = src
        }
    )
    return p
}

// const url = 'https://img.mukewang.com/5a9fc8070001a82402060220-140-140.jpg'
// loadImg(url).then(img => {
//     console.log(img.width)
//     return img
// }).then(img => {
//     console.log(img.height)
// }).catch(ex => console.error(ex))

const url1 = 'https://img.mukewang.com/5a9fc8070001a82402060220-140-140.jpg'
const url2 = 'https://img3.mukewang.com/5a9fc8070001a82402060220-100-100.jpg'

loadImg(url1).then(img1 => {
    console.log(img1)
    return img1 // 普通對象
}).then(img1 => {
    console.log(img1)
    return loadImg(url2) // promise 實例
}).then(img2 => {
    console.log(img2)
    return img2
}).then(img2 => {
    console.log(img2)
}).catch(ex => console.error(ex))

運行結果:

深入細節:當一個 Promise 完成(fulfilled)或者失敗(rejected)時,返回函數將被異步調用(由當前的線程循環來調度完成)。具體的返回值依據以下規則返回。如果 then 中的回調函數
①返回了一個值,那麼 then 返回的 Promise 將會成爲接受狀態,並且將返回的值作爲接受狀態的回調函數的參數值。

       執行then之後是會返回Promise對象的,比如這裏回調函數返回img1,然後將返回值img1作爲參數傳給下一個then的參數,這裏PromiseValue是img,因爲loadImg(url1)會執行resolve(img),resolve函數返回promise對象,這個img就是上面的img1,這是第一個url1加載出來的img


②沒有返回任何值,那麼 then 返回的 Promise 將會成爲接受狀態,並且該接受狀態的回調函數的參數值爲 undefined。

       比如下圖,沒有返回值的Promise對象的PromiseValue就是undefined


③拋出一個錯誤,那麼 then 返回的 Promise 將會成爲拒絕狀態,並且將拋出的錯誤作爲拒絕狀態的回調函數的參數值。

       這裏的③和⑤我準備一起說,所以要看例子請直接看到⑤的例子


④返回一個已經是接受狀態的 Promise,那麼 then 返回的 Promise 也會成爲接受狀態,並且將那個 Promise 的接受狀態的回調函數的參數值作爲該被返回的Promise的接受狀態回調函數的參數值。

       這裏因爲返回接受狀態的promise,所以傳給下一個then就是img,因爲執行了resolve(img),所以resolve函數返回的promise對象包含img,這是第二個url2加載出來的img,最後打印<img>標籤也和上面2個不同


⑤返回一個已經是拒絕狀態的 Promise,那麼 then 返回的 Promise 也會成爲拒絕狀態,並且將那個 Promise 的拒絕狀態的回調函數的參數值作爲該被返回的Promise的拒絕狀態回調函數的參數值。

       請求失敗的情況,並且最後沒有catch的情況下,PromiseStatus爲rejected

這裏到then裏面的回調函數return loadImg(....)的時候就是返回已經是拒絕狀態的Promise,即執行了reject(err)返回的Promise,那麼then返回的Promise也是拒絕狀態,往後傳參數的的時候,傳的err,下一個then的參數img2就是err(就是③中的將拋出的錯誤作爲拒絕狀態Promise的回調函數的參數值),所以這裏PromiseValue是Error: 圖片加載失敗 https://img3.mukewang123123.com/5a9fc8070001a82402060220-100-100.jpg at HTMLImageElement.img.onerror (<anonymous>:9:29)

 

這裏返回的Promise的狀態居然是resolved,error都打印了爲什麼還是resolved呢?因爲我們catch了,捕捉了錯誤,所以狀態執行正確,沒問題,如果去掉catch,那麼最後的拒絕狀態的Promise沒有被捕捉處理,PromiseStatus就是rejected


⑥返回一個未定狀態(pending)的 Promise,那麼 then 返回 Promise 的狀態也是未定的,並且它的終態與那個 Promise 的終態相同;同時,它變爲終態時調用的回調函數參數與那個 Promise 變爲終態時的回調函數的參數是相同的。

第一次請求不到則超時,PromiseStatus爲pending,最後加不加catch只是影響能不能捕捉到拒絕狀態Promise的問題,這裏不會影響Promise的pending狀態

 

2.setTimeout筆試題

運行結果

先打印1354,1s後打印2

更復雜的題目Promise和setTimeout混合的涉及到事件循環,大家可以看我另一篇博客:

用Promise的例子解釋事件循環EventLoop

 

關注、留言,我們一起學習。

 

===============Talk is cheap, show me the code================

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