js中的異步編程

併發(concurrency)和並行(parallelism)區別

併發是宏觀概念,我分別有任務 A 和任務 B,在一段時間內通過任務間的切換完成了這兩個任務,這種情況就可以稱之爲併發。

並行是微觀概念,假設 CPU 中存在兩個核心,那麼我就可以同時完成任務 A、B。同時完成多個任務的情況就可以稱之爲並行。

回調函數(Callback)

回調函數有一個致命的弱點,就是容易寫出回調地獄(Callback hell)

ajax(url, () => {
    // 處理邏輯
    ajax(url1, () => {
        // 處理邏輯
        ajax(url2, () => {
            // 處理邏輯
        })
    })
})

回調地獄的根本問題就是:

  1. 嵌套函數存在耦合性,一旦有所改動,就會牽一髮而動全身
  2. 嵌套函數一多,就很難處理錯誤
    回調函數還存在着別的幾個缺點,比如不能使用 try catch 捕獲錯誤,不能直接 return。

Generator

Generator 最大的特點就是可以控制函數的執行

	function *foo(x) {
        let y = 2 * (yield (x + 1))
        let z = yield (y / 3)
        return (x + y + z)
    }
    let it = foo(5)
    console.log(it.next())   // => {value: 6, done: false}
    console.log(it.next(12)) // => {value: 8, done: false}
    console.log(it.next(13)) // => {value: 42, done: true}
  1. 首先 Generator 函數調用和普通函數不同,它會返回一個迭代器
  2. 當執行第一次 next 時,傳參會被忽略,並且函數暫停在 yield (x + 1) 處,所以返回 5 + 1 = 6
  3. 當執行第二次 next 時,傳入的參數等於上一個 yield 的返回值,如果你不傳參,yield 永遠返回 undefined。此時 let y = 2 * 12,所以第二個 yield 等於 2 * 12 / 3 = 8
  4. 當執行第三次 next 時,傳入的參數會傳遞給 z,所以 z = 13, x = 5, y = 24,相加等於 42

Generator 函數一般見到的不多,其實也於他有點繞有關係,並且一般會配合 co 庫去使用。當然,我們可以通過 Generator 函數解決回調地獄的問題。

function *fetch() {
    yield ajax(url, () => {})
    yield ajax(url1, () => {})
    yield ajax(url2, () => {})
}
let it = fetch()
let result1 = it.next()
let result2 = it.next()
let result3 = it.next()

Promise

Promise的作用
Promise 翻譯過來就是承諾的意思,這個承諾會在未來有一個確切的答覆,並且該承諾有三種狀態,分別是: 等待中(pending);完成了 (resolved);拒絕了(rejected)
這個承諾一旦從等待狀態變成爲其他狀態就永遠不能更改狀態了,也就是說一旦狀態變爲 resolved 後,就不能再次改變。
當我們在構造 Promise 的時候,構造函數內部的代碼是立即執行的
Promise 實現了鏈式調用,也就是說每次調用 then 之後返回的都是一個 Promise,並且是一個全新的 Promise,原因也是因爲狀態不可變。如果你在 then 中 使用了 return,那麼 return 的值會被 Promise.resolve() 包裝

Promise.resolve(1)
  .then(res => {
    console.log(res) // => 1
    return 2 // 包裝成 Promise.resolve(2)
  })
  .then(res => {
    console.log(res) // => 2
  })

Promise 也很好地解決了回調地獄的問題

ajax(url)
  .then(res => {
      console.log(res)
      return ajax(url1)
  }).then(res => {
      console.log(res)
      return ajax(url2)
  }).then(res => console.log(res))

async 及 await

一個函數如果加上 async ,那麼該函數就會返回一個 Promise

async function test() {
  return "1"
}
console.log(test()) // -> Promise {<resolved>: "1"}

async 就是將函數返回值使用 Promise.resolve() 包裹了下,和 then 中處理返回值一樣,並且 await 只能配套 async 使用

async function test() {
  let value = await sleep()
}

async 和 await 可以說是異步終極解決方案了,相比直接使用 Promise 來說,優勢在於處理 then 的調用鏈,能夠更清晰準確的寫出代碼,畢竟寫一大堆 then 也很噁心,並且也能優雅地解決回調地獄問題。當然也存在一些缺點,因爲 await 將異步代碼改造成了同步代碼,如果多個異步代碼沒有依賴性卻使用了 await 會導致性能上的降低。
舉例:

	let a = 0
    a++
    console.log('1', a) // -> '1' 1
    b(a)//執行b函數
    async function b (num){
        await c(num)//將異步變爲同步,所以會提前執行c函數,再執行num++
        num++
        console.log('3', num) // -> '3' 2
    }
    function c (num){
        num = num + 10
        console.log('2', num) // -> '2' 11
    }

await 內部實現了 generator,其實 await 就是 generator 加上 Promise 的語法糖,且內部實現了自動執行 generator。

常用定時器函數

  1. setTimeout 定時器
  2. setInterval 計時器
  3. requestAnimationFrame

有循環定時器的需求,其實完全可以通過 requestAnimationFrame 來實現。首先 requestAnimationFrame 自帶函數節流功能,基本可以保證在 16.6 毫秒內只執行一次(不掉幀的情況下),並且該函數的延時效果是精確的,沒有其他定時器時間不準的問題。

function setInterval(callback, interval) {
  let timer
  const now = Date.now
  let startTime = now()
  let endTime = startTime
  const loop = () => {
    timer = window.requestAnimationFrame(loop)
    endTime = now()
    if (endTime - startTime >= interval) {
      startTime = endTime = now()
      callback(timer)
    }
  }
  timer = window.requestAnimationFrame(loop)
  return timer
}

let a = 0
setInterval(timer => {
  console.log(1)
  a++
  if (a === 3) cancelAnimationFrame(timer)
}, 1000)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章