Javascript非同步程式設計總結

pengyaoli發表於2020-09-25

前言

由於js涉及dom互動,如果多執行緒會出現很複雜當執行緒同步問題,故js是單執行緒模式,單執行緒模式指執行程式碼當執行緒只有一個,故每次只能執行一個任務
單執行緒當優點為簡單,安全,缺點為耗時任務會阻塞程式碼執行,出現白屏假死等影響體驗的現象
故為了解決阻塞問題,js將任務的執行分為同步和非同步兩種模式

同步模式

指程式碼中任務依次執行,後一個任務必須等前一個任務執行完畢才會執行,任務等執行順序與程式碼的編寫順序一致,大部分任務均為同步執行
當程式碼載入進來後,會由自執行函式包裹所有程式碼,按照從上倒下,先把第一行放到呼叫棧,當程式碼執行完畢,彈出程式碼,再執行下一行程式碼,知道都執行完畢

非同步模式

不會等待任務執行完畢,才去執行下一任務,開啟以後就會執行下一個任務,後續的處理邏輯會通過回撥函式方式處理
在主執行緒外開闢新的執行緒,在不阻塞主執行緒程式碼大情況下,用於處理一些耗時或計算量大的任務,當非同步結束後,把回撥函式加入到訊息佇列中,等待事件迴圈的呼叫

EventLoop

事件迴圈會監聽呼叫棧和佇列,在呼叫棧空了後,事件迴圈會從佇列中拿出第一個任務,放到呼叫棧中執行,執行完畢後,再拿出下一個任務,依次迴圈

訊息佇列

訊息佇列是儲存待執行的非同步回撥函式,並供事件迴圈呼叫當場所,當非同步程式碼觸發回撥函式時,會把此函式放在佇列末尾,事件迴圈每次在佇列當最前方取出函式去執行

巨集任務與微任務

微任務有js的Promise和nodejs的process.nexttick等
巨集任務有setTimeout,setInterval,js主程式碼等
巨集任務和微任務是相對於非同步程式碼的,在事件迴圈時,當微任務佇列中存在任務,那麼會先呼叫所有微任務,全部執行完畢,再去呼叫第一個巨集任務,依次迴圈

非同步模式執行步驟

console.log('begin')
setTimeout(function timer1 () {
  console.log('timer1')
}, 1800)
console.log('end')

上方簡單的非同步程式碼,首先載入完所有程式碼後,會把程式碼放入一個自執行函式並把第一行同步程式碼壓入呼叫棧,執行完畢後列印begin,然後把定時器壓入呼叫棧,會開啟一個執行緒用於計時,然後推出,再把end列印壓入呼叫棧,列印end,再推出,執行完畢,呼叫棧為空
當計時到1.8s時,會把定時器的回撥函式放到訊息佇列,事件迴圈監聽到佇列變更後,在呼叫棧為空時,會把佇列的第一個推入呼叫棧(如果此時有微任務,會執行完所有微任務再執行此操作),執行定時器的回撥函式,列印timer1

Promise

非同步程式設計的根基是回撥函式,而傳統的回撥函式巢狀寫法,很難維護與閱讀,promise可以使非同步程式設計更可維護與閱讀

new Promise(function (resolve, reject) {
  // 同步執行此函式體
  setTimeout(() => resolve(1))
}).then((v) => {
  console.log(v); // 1
});
狀態有fulFilled(成功,對應resolve,執行then的第一個引數),rejected(失敗,對應rejected,執行catch回撥或then的第二個引數)

Promise.all()

引數為Promise物件組成的陣列,返回新的Promise,當陣列中所有的Promise的狀態均為fulFilled,執行then回撥,引數為所有Promise的返回值組成的陣列,此陣列與傳入陣列一一對應,否則執行catch

Promise.race()

引數為Promise物件組成的陣列,返回新的Promise,當陣列中有一個任務結束,則直接按照該任務的狀態返回

Promise.resolve()

快速把值轉化為Promise物件,並執行then的第一個引數,如傳入Promise物件,會直接返回

Promise.reject()

快速把值轉化為Promise物件,並執行then的第二個引數,如傳入Promise物件,會直接返回

Promise.finally()

引數為函式,返回值為Promise物件,不論成功或失敗均會呼叫,並且所傳函式呼叫時沒有引數,會把當前Promise的成功或失敗的值,傳遞給返回的新的Promise物件

Generator

使用*和yield關鍵字,當呼叫函式時,不會直接執行,而是會返回Generator物件,當呼叫物件當next方法時,才會執行,並且遇到yield關鍵字會停止執行,直到再次呼叫next方法

function * foo () {
    const res = yield 'foo';
	return res;
}
const generator = foo()
const result = generator.next(); // foo

因為每次均須手動呼叫next方法,所以需要一個執行器去自動執行所有next方法

function co (generator) {
  const g = generator()
  function handleResult (result) {
    if (result.done) return // 生成器函式結束
    result.value.then(data => {
      handleResult(g.next(data))
    }, error => {
      g.throw(error)
    })
  }
  handleResult(g.next())
}
co(foo)

async await

async await是generator的語法糖,可不用自己呼叫自己封裝執行器,看起來更像同步程式碼

async function main () {
    const users = await ajax('/api/users.json')
    const posts = await ajax('/api/posts.json')
    const urls = await ajax('/api/urls.json')
}

相關文章