你們都知道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
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一般與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')這四個異步操做就能夠併發執行了。
這篇文章簡單的介紹了一些經常使用的異步編程的方法,若是有錯誤或不嚴謹的地方,歡迎批評指正,若是喜歡,歡迎點贊收藏。