異步編程小結

在javascript單線程的世界裏,沒有異步步履維艱。本章節介紹異步編程的發展,從callback,Eventspromise,generator,async/await.javascript

爲何要使用異步編程

在(javascript)單線程的世界裏,若是有多個任務,就必須排隊,前面一個完成,再繼續後面的任務。就像一個ATM排隊取錢似的,前面一個不取完,就不讓後面的取。 爲了這個問題,javascript提供了2種方式: 同步和異步。 異步就像銀行取錢填了單子約了號,等着叫到號,再去作取錢,等待的時間裏還能夠乾點其餘的事兒~html

異步回調帶來的問題

回調地獄 (Callbacks Hell)

舉個例子:經過api拿到數據,數據裏面有圖片,圖片加載成功渲染,那麼代碼以下:java

// 僞代碼
request(url, (data) => {
    if(data){
        loadImg(data.src, () => {
            render();
        })
    }
})
複製代碼

若是有在業務邏輯比較複雜或者NodeJS I/O操做比較頻繁的時候,就成了下面這個樣子:node

doSth1((...args, callback) => {
    doSth2((...args, callback) => {
        doSth3((...args, callback) => {
            doSth4((...args, callback) => {
                doSth5((...args, callback) => {

                })
            })
        })
    })
})
複製代碼

這樣的維護性可讀性,整我的瞬間感受很差了~jquery

異常處理

try {
    setTimeout(() => {
        throw new Error('unexpected error');
    }, 100);
} catch (e) {
    console.log('error2', e.message);
}
複製代碼

以上代碼運行拋出異常,但try catch不能獲得將來時間段的異常。git

流程控制不方便

流程控制只能經過維護各類狀態來處理,不利於管理es6

異步編程現有解決方案對比

事件機制

無論瀏覽器仍是NodeJS,提供了大量內置事件API來處理異步。github

事件監聽

瀏覽器中如: websocket, ajax, canvas, imgFileReader等 NodeJS如: stream, httpweb

自定義事件(本質上是一種發佈訂閱模式)

  • NodeJS中的EventEmitter事件模型
  • 瀏覽器中:如DOM可以使用addEventListener,此外瀏覽器也提供一些自定義事件的API,但兼容性很差,具體能夠這篇文章;也能夠用Node中的EventEmitter;jquery中也對此作了封裝,on,bind等方法支持自定義事件。

事件小結

事件必定程度上解決了解耦和提高了代碼可維護性;對於異常處理,只有部分支持相似error事件才能處理。若想實現異常處理機制,只有本身模擬error事件,比較繁瑣。ajax

Promise

Promise嚴格來講不是一種新技術,它只是一種機制,一種代碼結構和流程,用於管理異步回調。爲了統一規範產生一個Promise/A+規範,點擊查看Promise/A+中文版,cnode的William17實現了Promise/A+規範,有興趣的能夠點這裏查看

  • promise狀態由內部控制,外部不可變
  • 狀態只能從pendingresovled, rejected,一旦進行完成不可逆
  • 每次then/catch操做返回一個promise實例,能夠進行鏈式操做

promise狀態
部分代碼以下:

readFile(path1).then(function (data) {
    console.log(data.toString());
    return readFile(path2);
}).then(function (data) {
    console.log(data.toString());
    return readFile(errorPath);
}).then(function (data) {
    console.log(data.toString());
}).catch(function (e) {
    console.log('error', e);
    return readFile(path1);
}).then(function (data) {
    console.log(data.toString());
});
複製代碼

Promise的缺陷:

  • 內部拋出錯誤只能經過promise.catch才能才能接收到
  • 語義不明確

Generator

generator介紹

Generator是ES6提供的方法,是生成器,最大的特色:能夠暫停執行和恢復執行(能夠交出函數的執行權),返回的是指針對象.

  • 須要自執行函數才能持續運行,不然須要手工調用流程
  • 自執行函數借助promise, 獲取異常和最終結果

generator 和 promise自執行實現

const run = function (generator) {
  var g = generator()
  var perform = function (result) {
    if (result.done === true) {
      return result.value
    }
    if (isPromise(result.value)) {
      return result.value.then(function (v) {
        return perform(g.next(v))
      }).catch(function (e) {
        return perform(g.throw(e))
      })
    } else {
      return perform(g.next(result.value))
    }

  }
  return perform(g.next())
}

const isPromise = f => f.constructor === Promise

function* g() {
  var a = yield sleep(1000, _ => 1)
  var b = yield sleep(1000, _ => 2)
  return a + b
}
function sleep(d, fn) {
  return new Promise((resolve, reject) => {
    setTimeout(_ => resolve(fn()), d)
  })
}
複製代碼

因爲以上問題,因而一個叫 co庫誕生,支持thunkPromise. 關於thunk能夠查看阮一峯老師的thunk介紹和應用

Async/Await

ES7提供了async函數,使得異步操做變得更加方便。

  • 內置執行器
  • 更好的語義
  • 更多的實用場景,co參數Generator函數中yield只能是promisethunk

實例代碼:

async function asyncReadFile() {   
    var p1 = readFile(path.join(__dirname, '../data/file1.txt'));
    var p2 = readFile(path.join(__dirname, '../data/file2.txt'));
    var [f1, f2] = await Promise.all([p1, p2]);
    return `${f1.toString()}\n${f2.toString()}`;
}
(async function () {
    try {
        console.log(await asyncReadFile());
    } catch (e) {
        console.log(e.message)
    }
})();
複製代碼

Node8 async/await 支持

Node8.0發佈,全面支持 async/await, 推薦使用 async/await, 低版本node能夠使用 babel來編譯處理。 而 爲了方便 接口設計時 返回 promise 更方面使用者. 固然依然使用 callback , 經過 promisify作轉換, Node8.0已經內置 util.promisify方法。

參考資料

小結

異步編程在javascript中扮演者重要的角色,雖然如今須要經過babel,typescript等編譯或轉換代碼,跟着規範標準走,就沒有跑偏。

很久以前在github博客上的文章了。

如需轉載,請備註出處。

相關文章
相關標籤/搜索