JavaScript 非同步編程

部落格地址://ainyi.com/96

眾所周知,JavaScript 是單執行緒的,但非同步在 js 中很常見,那麼簡單來介紹一下非同步編程

同步編程和非同步編程

同步編程,電腦一行一行按順序依次執行程式碼,當前程式碼任務執行時會阻塞後續程式碼的執行;典型的請求-響應模型就是這樣,當請求調用一個函數或方法後,需等待其響應返回,然後執行後續程式碼

非同步編程,執行當前任務時(執行中),也可直接執行下一個任務;多個任務並發執行

這就涉及到兩個比較容易混淆的概念了:並行並發

並行(parallel):指同一時刻內多任務同時進行;如下圖:
755737372912ea8e89c4007.jpg

並發(concurrency):指在同一時間段內,多任務同時進行著,但是同一時刻,只有某一任務執行。使得在宏觀上具有多個進程同時執行的效果,但在微觀上只是把時間分成若干段,使多個進程快速交替地執行;如下圖:
7557373da64ffd6d1effaac.jpg

非同步機制

由上面並發的解釋,可以知道單執行緒可以實現類似多執行緒機制的這種執行方式;那麼 JavaScript 單執行緒的非同步編程可以實現多任務並發執行

重點實現 js 非同步的方式,就是事件循環,之前寫過關於事件循環的例子,可看:JavaScript 事件循環、非同步和同步

事件循環

事件循環涉及到兩個概念:消息隊列、任務

消息隊列:也叫任務隊列,存儲待處理消息及對應的回調函數或事件處理程式
任務:js 區分同步任務和非同步任務,程式碼執行就是在執行任務,也就是對應同步和非同步的程式碼塊

首先 JavaScript 的同步任務是進入主執行緒的執行棧執行;非同步任務則進入消息隊列(任務隊列),一個存儲著待執行任務的隊列,嚴格按照時間先後順序執行,排在隊頭的任務將會率先執行,而排在隊尾的任務會最後執行

事件循環的流程:檢查主執行緒執行棧是否為空,先執行執行棧中的同步任務,非同步任務(回調函數)放入任務隊列中,一旦執行棧中的所有的同步任務執行完畢,就會取出任務隊列的首部壓入執行棧,開始執行,然後繼續檢查執行棧是否為空,重複這個過程

簡單來說:事件循環其實就是入棧出棧的循環

這樣就能實現非同步方式

js 的非同步方式

  • setTimeout
  • ajax
  • Promise
  • Generator

setTimeout
即使將時間設置為 0,也會延遲執行,即非同步執行。具體可看:setTimeout 時間參數為 0 的探討

setTimeout(() => { 
   console.log('Hello!')
}, 0)

ajax

let xhr = new XMLHttpRequest()
xhr.onreadystatechange = function() { 
  if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304 ) {       
    console.log(xhr.responseText)
  } else {   
    console.log( xhr.status)
  }
}
xhr.open('GET', 'url', false)
xhr.send()

xhr.open 中第三個參數默認為 false 非同步執行,改為 true 時為同步執行

Promise
promise 就經常使用了,平常使用 axios 作為請求介面的方式,就是封裝了 Promise。當然也可以自己封裝使用
具體可看:ES6 Promise 解析及詳解三個狀態

const promise = new Promise(resolve => {
  setTimeout(() => {
    resolve('hello')
  }, 1000)})
promise.then(value => {
  console.log(value, 'world')
}, error =>{
  console.log(error, 'unhappy')
})

Generator
generator 也叫做生成器,它是 ES6 中引入的一種新的函數類型,內部擁有能夠多次啟動和暫停程式碼執行的強大能力,那麼也能夠用於非同步編程中

const axios = require('axios')

const foo = function () {
  return axios({
    method: 'GET',
    url: '//cosmos-alien.com/some.url'
  })
}

const main = function *() {
  try {
    let result = yield foo()
    console.log(result)
  } catch (err) {
    console.error(err)
  }
}

let it = main()
let p = it.next().value

p.then((data) => {
  it.next(data)
}, (err) => {
  it.throw(err)
})

部落格地址://ainyi.com/96