js 異步編程

你們都知道js的執行環境是單線程的,若是沒有異步編程,那麼js的執行效率會很是低下,致使程序十分卡頓,一提到異步編程你們首先的想到的必定是回調函數,這也是最經常使用的異步編程的形式,但其實經常使用的還有Promise和Async函數,接下來就讓咱們一塊兒學習這幾種經常使用的異步編程方法。javascript

回調函數

回調函數就是把任務的第二段單獨寫在一個函數裏面,等到從新執行這個任務的時候,就直接調用這個函數,來看一個簡單的例子:java

function print(name, callback) {
  setTimeout(() => {
    console.log(name)
    if (callback) {
      callback()
    }
  }, 1000)
}
print('a', function () {
  print('b')
})

上面這個例子中將print('b')放在print('a')的回調函數中,這樣就能按順序依次打印a、b,可是回調函數有一個很明顯的問題,就是當回調函數嵌套過深時,會致使代碼混亂,不夠清晰,這就是人們常說的對調地獄,來看下面這個例子:es6

function print(name, callback) {
  setTimeout(() => {
    console.log(name)
    if (callback) {
      callback()
    }
  }, 1000)
}
print('a', function () {
  print('b', function () {
    print('c', function () {
      print('d')
    })
  })
})

當咱們想按順序依次打印a、b、c、d時,代碼就變成上面的樣子,能夠看到,咱們的代碼造成四層嵌套,若是還要加回調函數就要繼續嵌套,這樣嵌套會越寫越深,愈來愈難以維護,此時咱們就必須考慮用新的技術去改進,es6的Promise函數應運而生,接下來讓咱們看Promise函數是如何改進這個問題的。npm

Promise

function print(name) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(name)
      resolve()
    }, 1000)
  })
}
print('a').then(() => {
  return print('b')
})
  .then(() => {
    return print('c')
  })
  .then(() => {
    return print('d')
  })

和以前用回調函數的形式相比,Promise函數寫法更加清晰,由回調函數的嵌套調用變成了鏈式調用,可是Promise也有一個很嚴重的問題就是代碼冗餘,原來的任務被Promise包裝了一下,無論什麼操做都是放在then函數裏面,致使代碼的語以變差,有什麼更好的解決辦法呢?若是您對Promise函數還想有更深刻的瞭解,能夠去看阮一峯老師es6入門編程

Async

在正式使用異步函數以前,先簡單的介紹一下它的用法,async一般與await一塊兒使用,async函數返回一個Promise對象,可使用then方法添加回調函數。當函數執行的時候,一旦遇到await就會先返回,等到觸發的異步操做完成,再接着執行函數體後面的語句。作了簡單的介紹後,接下來,咱們來async函數是怎麼對Promise調用優化的。看下面的例子:數組

function print(name) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(name)
      resolve()
    }, 1000)
  })
}
async function test () {
  await print('a')
  await print('b')
  await print('c')
  await print('d')
}
test()

async函數來處理以前的問題,代碼就是上面的這個例子中所展現的樣子,是否是感受代碼瞬間清晰了,並且代碼更加好理解了,再仔細思考一下使用async異步函數就很完美了嗎?其實async異步函數也有其固有的問題,接下來咱們就看看async異步函數還有什麼問題須要解決。promise

錯誤捕獲

異步函數第一個須要解決的問題就是錯誤捕獲的問題,讓咱們看看通常狀況下async異步函數是怎麼作錯誤捕獲的,來看一個例子:併發

function print(name) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(name)
      resolve()
    }, 1000)
  })
}
async function test () {
  try {
    await print('a')
  } catch (err) {
    console.log(err)
  }
}
test()

當使用上述形式的try,catch進行錯誤捕獲的時候,是否是以爲代碼和使用Promise函數時同樣囉嗦,那有沒有好的解決辦法呢?讓咱們來看另一個例子:異步

function print(name) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(name)
      resolve('a')
    }, 1000)
  })
}
async function test () {
  let [ err, result ] = await to(print('a'))
  if (err) throw err
  return result
}
test()

to.js:async

function to(promise, errorExt) {
  return promise
    .then(function (data) { return [null, data]; })
    .catch(function (err) {
      if (errorExt) {
        Object.assign(err, errorExt);
      }
      return [err, undefined];
    });
}

export { to };
export default to;

上述例子中,將async異步函數的錯誤處理封裝到了一個to.js中,這裏面其實只有一個簡單方法,傳入一個Promise對象,對Promise對象進行錯誤捕獲返回值,用解構的形式獲取返回值和錯誤,這樣就不須要反覆寫try catche作錯誤捕獲了。to.js是一個開源庫

異步陷阱

什麼是異步陷阱呢?在使用async異步函數的時候,多個異步操做是能夠同時執行,可是有await命令變成了繼發的形式了,即必須等待前一個執行完了後一個才能執行,仍是以前的例子:

function print(name) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(name)
      resolve()
    }, 1000)
  })
}
async function test () {
  await print('a')
  await print('b')
  await print('c')
  await print('d')
}
test()

假設await print('a')、await print('b')、await print('c')、await print('d')這四個操做並無前後的邏輯關係,能夠同時執行,那麼按照上面的寫法就會致使前一個執行完再執行下一個,整個執行過程當中的等待時間會有4s,可是同時執行的等待時間就只有1s,這是在使用async異步函數會常常忽略的一個問題,那麼怎麼解決呢?介紹一個我常用的辦法,看例子:

function print(name) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(name)
      resolve('a')
    }, 1000)
  })
}
async function test () {
  Promise.all([print('a'), print('b'), print('c'), print('d')])
}
test()

其實解決辦法很簡單就是經過Promise.all()方法,將全部異步操做做爲參數數組傳入,這樣print('a')、print('b')、print('c')、print('d')這四個異步操做就能夠併發執行了。

總結

這篇文章簡單的介紹了一些經常使用的異步編程的方法,若是有錯誤或不嚴謹的地方,歡迎批評指正,若是喜歡,歡迎點贊收藏。

相關文章
相關標籤/搜索