深入淺出 JS 非同步處理技術方案,js技術方案

來源:互聯網
上載者:User

深入淺出 JS 非同步處理技術方案,js技術方案


本文來自作者 icepy 在 GitChat 上分享 「深入淺出 JS 非同步處理技術方案」,「閱讀原文」查看交流實錄。

「文末高能」

編輯 | 哈比

為什麼要非同步

“當我們在星巴克買咖啡時,假設有 100 個人在排隊,也許咖啡的下單只要 10 秒,但是咖啡的製作到客人領取咖啡要 1000 秒。如果在同步的情境下,第一個客人下單到領取完咖啡要 1010 秒才能輪到下一個客人,這在效率(某些情境)上來說會比較低下。

如果我們非同步處理這個流程,客人下單 10 秒拿到憑證,客人就可以去做別的事情,並且 10 秒後下一個客人可以繼續下單,並不阻礙流程。反而可以通過憑證,讓客人拿到自己的咖啡,也許時間上並不是第一個下單的客人先拿到。

在網頁的世界裡也是同樣的道理,不妨我們看看在執行 JS 代碼的主線程裡,如果遇到了 AJAX 請求,使用者事件等,如果不採用非同步方案,你會一直等待,等待第一個耗時的處理完成才能接上下一個 JS 代碼的執行,於是介面就卡住了。

也許有人會想,既然大家都說現在網頁上效能損耗最大的屬於 DOM 節點的操作,把這些搞成非同步,行不行?其實這會帶來一個不確定性問題:既 “成功” 的狀態到底誰先來的問題。

可以想象一下,如果我們在操作 DOM,既給節點新增內容,也給節點刪除,那麼到底以誰為基準呢?考慮到複雜性,也就可見一斑了。

Event loop

雖然非同步與 event loop 沒有太直接的關係,準確的來講 event loop 只是實現非同步一種機制。(瞭解為主)

還是以上面咖啡館為例子,假定情境還是 100 人,這 100 人除了下單是與咖啡本身有關聯之外,其餘的時間,比如看書,玩遊戲的等可以視為自己的執行邏輯。

如果用 event loop 來給它做一個簡單的畫像,那麼它就像:在與咖啡店店員溝通下單視為主執行棧,咖啡的製作可以視為一個非同步任務,添加到一個任務隊列裡,一直等帶 100 個人都下單完成,然後開始讀取任務隊列中的非同步任務,事件名就是下單憑證,如果有對應的 handler,那麼就執行叫對應的客人來領取咖啡。

這個過程,是迴圈不斷的。假設沒有客人來下單的時候,也就是店員處於空閑時間(可能自己去搞點別的)。

傳統的 Callback

假定一個 asyncFetchDataSource 函數用於擷取遠端資料源,可能有 20 秒。


function asyncFetchDataSource(cb){    (… 擷取資料 , function(response){    typeof cb === 'function' && cb(response)    })    }

這種形式的 callback 可以適用於簡單情境,如果這裡有一個更複雜的情境,比如擷取完資料來源之後,依據 id,擷取到某個資料,在這某個資料中再依據 id 來更新某個列表,可以遇見的能看到代碼變成了:


asyncFetchDataSource('',function(data_a){    const { id_a } = data_a    asyncFetchDataSource( id_a,function(data_b){    const { id_b } = data_b        asyncFetchDataSource(id, function(data_c){        })    })})

如果有極端情況出現,這裡的 callback 就會變成無極限了。

Thunk 函數

這是一種 “傳名調用” 的策略,表現的形式就是將參數放入一個臨時函數,然後再將這個臨時函數傳入函數體內。


function asyncFetchDataSource(url){    return function(callback){    fetch(url, callback)    }}const dataSource = asyncFetchDataSource('https://github.com/icepy');dataSource(function(data){})

Promise

Promise 正是想來處理這樣的非同步編程,如果我們用 Promise 該如何處理一段 Ajax?


function fetch(){  return new Promise(function(resolve,reject){    $.ajax({      url: 'xxx',      success:function(data){        resolve(data)      },      error:function(error){        reject(error)      }    })  })}fetch().then(function(data){}).catch(function(error){})

Promise 聲明周期:

  • 進行中(pending)

  • 已經完成(fulfilled)

  • 拒絕(rejected)

如同上面 Ajax 的例子,我們可以很好的封裝一個函數,讓 fetch 函數返回一個 Promise 對象。

在 Promise 建構函式裡,可以傳入一個 callback,並且在這裡完成主體邏輯的編寫。唯一需要注意的是:Promise 對象只能通過 resolve 和 reject 函數來返回,在外部使用 then 或 catch 來擷取。

如果你直接拋出一個錯誤(throw new Error(‘error’)),catch 也是可以正確的捕獲到的。

Promise 其他的方法

Promise.all(當所有在可迭代參數中的 promises 已完成,或者第一個傳遞的 promise(指 reject)失敗時,返回 promise。)


var p1 = Promise.resolve(3);var p2 = 1337;var p3 = new Promise((resolve, reject) => {  setTimeout(resolve, 100, "foo");});Promise.all([p1, p2, p3]).then(values => {  console.log(values); // [3, 1337, "foo"]});

Promise.race(返回一個新的 promise,參數 iterable 中只要有一個 promise 對象 “ 完成(resolve)” 或 “ 失敗(reject)”,新的 promise 就會立刻 “ 完成(resolve)” 或者 “ 失敗(reject)”,並獲得之前那個 promise 對象的傳回值或者錯誤原因。)


var p1 = new Promise(function(resolve, reject) {    setTimeout(resolve, 500, "one");});var p2 = new Promise(function(resolve, reject) {    setTimeout(resolve, 100, "two");});Promise.race([p1, p2]).then(function(value) {    console.log(value); // "two"    // 兩個都完成,但 p2 更快});

有趣的是如果你使用 ES6 的 class,你是可以去派生 Promise 的。


class MePromise extends Promise{  // 處理 ...}

Generator

Generator 可以輔助我們完成很多複雜的任務,而這些基礎知識,又與 iterator 息息相關。

舉一個很簡單的例子,相信有很多朋友,應該使用過 co 這個非同步編程的庫,它就是用 Generator 來實現,當然它的設計會比例子要複雜的多,我們先來看一個 co 簡單的用法:


import co from 'co'co(function* () {  var result = yield Promise.resolve(true);  return result;}).then(function (value) {  console.log(value);}, function (err) {  console.error(err.stack);});

相應的,我們來實現一個簡化的版本:


function co(task){  let _task = task()  let resl = _task.next();  while(!resl.done){    console.log(resl);    resl = _task.next(resl.value);  }}function sayName(){  return {    name: 'icepy'  }}function assign *(f){  console.log(f)  let g = yield sayName()  return Object.assign(g,{age:f});}co(function *(){  let info = yield *assign(18)  console.log(info)})

雖然,這個例子中,還不能很好的看出來 “非同步” 的情境,但是它很好的描述了 Generator 的使用方式。

從最開始的定義中,已經和大家說明了,Generator 最終返回的依然是一個迭代器對象,有了這個迭代器對象,當你在處理某些情境時,你可以通過 yield 來控制,流程的走向。

通過 co 函數,我們可以看出,先來執行 next 方法,然後通過一個 while 迴圈,來判斷 done 是否為 true,如果為 true 則代表整個迭代過程的結束,於是,這裡就可以退出迴圈了。在 Generator 中的傳回值,可以通過給 next 方法傳遞參數的方式來實現,也就是遇上第一個 yield 的傳回值。

有邏輯,自然會存在錯誤,在 Generator 捕獲錯誤的時機與執行 throw 方法的順序有關係,一個小例子:


let hu = function *(){  let g = yield 1;  try {    let j = yield 2;  } catch(e){    console.log(e)  }  return 34}let _it = hu();console.log(_it.next())console.log(_it.next())console.log(_it.throw(new Error('hu error')))

當我能捕獲到錯誤的時機是允許完第二次的 yield,這個時候就可以 try 了。

async await

async function createNewDoc() {    let response = await db.post({}); // post a new doc    return await db.get(response.id); // find by id}

https://tc39.github.io/ecmascript-asyncawait/

根據規範規定一個 asnyc 函數總是要返回一個 Promise,從代碼直觀上來說,雖然簡潔了,但是 async await 並未萬能,它有很大的局限性,比如:

  • 因為是順序執行,假設有三個請求,那麼這裡並沒有很好的利用到非同步帶來的止損(再封裝一個 Promise.all);

  • 如果要捕獲異常,需要去包 try catch;

  • 缺少控制流程程,比如 progress(進度)pause,resume 等周期性的方法;

  • 沒有打斷的功能。

主流的非同步處理方案

我喜歡用 co,而且社區使用也很廣泛,https://github.com/tj/co。


co(function* () {  var result = yield Promise.resolve(true);  return result;}).then(function (value) {  console.log(value);}, function (err) {  console.error(err.stack);});

babel polyfill 支援,在瀏覽器環境中使用非同步解決方案

如果你想使用全的 polyfiil,直接 npm install —save babel-polyfill,然後在 webpack 裡進行配置即可。


module.exports = {  entry: ["babel-polyfill", "./app/js"]};

當然由於我目前的開發基於的瀏覽器都比較高,所以我一般是挑選其中的:

https://github.com/facebook/regenerator/tree/master/packages/regenerator-runtime

https://github.com/facebook/regenerator/tree/master/packages/regenerator-transform

如果你要使用 async await 配置上 http://babeljs.io/docs/plugins/transform-async-to-generator/ 即可

Node.js 環境中使用非同步解決方案

由於本人的 node 使用的 LTS 已經是 8.9.3 版本了,所以大部分情況下已經不再使用 babel 去進行轉換,而是直接使用 co 這樣的庫。

當然 co 也不是萬能,一定要根據業務情境,與其他非同步處理的方式,配合中使用。

總結

相信未來的 JS 編程,只會越來越簡單,不要拘泥於文法,語言上的特性,不妨多看一看 “外面的世界”。

近期熱文

《敏捷教練 V 形六步法實戰:從布朗運動到深度協作》

《從零開始,搭建 AI 音箱 Alexa Voice Messaging Service》

《修改訂單金額!?0.01 元購買 iPhoneX?| Web談邏輯漏洞》

《讓你一場 Chat 學會 Git》

《介面測試載入器 Postman 使用實踐》

《如何基於 Redis 構建應用程式組件》

《深度學習在攝影技術中的應用與發展》


「閱讀原文」看交流實錄,你想知道的都在這裡

著作權聲明:本文為GitChat作者的原創文章,未經 GitChat 允許不得轉載。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.