深入探析koa之非同步回調處理篇

来源:http://www.cnblogs.com/malingyang/archive/2016/11/19/6080074.html
-Advertisement-
Play Games

在上一篇中我們梳理了koa當中中間件的洋蔥模型執行原理,並實現了一個可以讓洋蔥模型自動跑起來的流程管理函數。這一篇,我們再來研究一下koa當中非同步回調同步化寫法的原理,同樣的,我們也會實現一個管理函數,是的我們能夠通過同步化的寫法來寫非同步回調函數。 1. 回調金字塔及理想中的解決方案 我們都知道ja ...


在上一篇中我們梳理了koa當中中間件的洋蔥模型執行原理,並實現了一個可以讓洋蔥模型自動跑起來的流程管理函數。這一篇,我們再來研究一下koa當中非同步回調同步化寫法的原理,同樣的,我們也會實現一個管理函數,是的我們能夠通過同步化的寫法來寫非同步回調函數。

1. 回調金字塔及理想中的解決方案

我們都知道javascript是一門單線程非同步非阻塞語言。非同步非阻塞當然是它的一個優點,但大量的非同步操作必然涉及大量的回調函數,特別是當非同步嵌套的時候,就會出現回調金字塔的問題,使得代碼的可讀性非常差。比如下麵一個例子:

var fs = require('fs');

fs.readFile('./file1', function(err, data) {
  console.log(data.toString());
  fs.readFile('./file2', function(err, data) {
    console.log(data.toString());
  })
})

這個例子是先後讀取兩個文件內容並列印,其中file2的讀取必須在file1讀取結束之後再進行,因此其操作必須要在file1讀取的回調函數中執行。這是一個典型的回調嵌套,並且只有兩層而已,在實際編程中,我們可能會遇到更多層的嵌套,這樣的代碼寫法無疑是不夠優雅的。

在我們想象中,比較優雅的一種寫法應該是看似同步實則非同步的寫法,類似下麵這樣:

var data;
data = readFile('./file1');
//下麵的代碼是第一個readFile執行完畢之後的回調部分
console.log(data.toString());
//下麵的代碼是第二個readFile的回調
data = readFile('./file2');
console.log(data.toString());

這樣的寫法,就完全避免回調地獄。事實上,koa就讓我們可以使用這樣的寫法來寫非同步回調函數:

var koa = require('koa');
var app = koa();
var request=require('some module');

app.use(function*() {
  var data = yield request('http://www.baidu.com');
  //以下是非同步回調部分
  this.body = data.toString();
})

app.listen(3000);

那麼,究竟是什麼讓koa有這麼神奇的魔力呢?

2. generator配合promise實現非同步回調同步寫法

關鍵的一點,其實前一篇也提到了,就是generator具有類似"打斷點"這樣的效果。當遇到yield的時候,就會暫停,將控制權交給yield後面的函數,當下次返回的時候,再繼續執行。

而在上面的那個koa例子中,yield後面的可不是任何對象都可以哦!必須是特定類型。在co函數中,可以支持promise, thunk函數等。

今天的文章中,我們就以promise為例來進行分析,看看如何使用generator和promise配合,實現非同步同步化。

依舊以第一個讀取文件例子來分析。首先,我們需要將讀文件的函數進行改造,將其封裝成為一個promise對象:

var fs = require('fs');

var readFile = function(fileName) {
  return new Promise(function(resolve, reject) {
    fs.readFile(fileName, function(err, data) {
      if (err) {
        reject(err);
      } else {
        resolve(data);
      }
    })
  })
}

//下麵是readFile使用的示例
var tmp = readFile('./file1');
tmp.then(function(data) {
  console.log(data.toString());
})

關於promise的使用,如果不熟悉的可以去看看es6中的語法。(近期我也會寫一篇文章來教大家如何用es5的語法來自己實現一個具備基本功能的promise對象,敬請期待呦\^_\^)

簡單來講,promise可以實現將回調函數通過 promise.then(callback)的形式來寫。但是我們的目標是配合generator,真正實現如絲般順滑的同步化寫法,如何配合呢,看這段代碼:

var fs = require('fs');

var readFile = function(fileName) {
  return new Promise(function(resolve, reject) {
    fs.readFile(fileName, function(err, data) {
      if (err) {
        reject(err);
      } else {
        resolve(data);
      }
    })
  })
}

//將讀文件的過程放在generator中
var gen = function*() {
  var data = yield readFile('./file1');
  console.log(data.toString());
  data = yield readFile('./file2');
  console.log(data.toString());
}

//手動執行generator
var g = gen();
var another = g.next();
//another.value就是返回的promise對象
another.value.then(function(data) {
  //再次調用g.next從斷點處執行generator,並將data作為參數傳回
  var another2 = g.next(data);
  another2.value.then(function(data) {
    g.next(data);
  })
})

上述代碼中,我們在generator中yield了readFile,回調語句代碼寫在yield之後的代碼中,完全是同步的寫法,實現了文章一開頭的設想。

而yield之後,我們得到的是一個another.value是一個promise對象,我們可以使用then語句定義回調函數,函數的內容呢,則是將讀取到的data返回給generator並繼續讓generator從斷點處執行。

基本上這就是非同步回調同步化最核心的原理,事實上如果大家熟悉python,會知道python中有"協程"的概念,基本上也是使用generator來實現的(我想當懷疑es6的generator就是借鑒了python~)

不過呢,上述代碼我們依然是手動執行的。那麼同上一篇一樣,我們還需要實現一個run函數,用於管理generator的流程,讓它能夠自動跑起來!

3. 讓同步化回調函數自動跑起來:一個run函數的編寫

仔細觀察上一段代碼中手動執行generator的部分,也能發現一個規律,這個規律讓我們可以直接寫一個遞歸的函數來代替:

var run=function(gen){
  var g;
  if(typeof gen.next==='function'){
    g=gen;
  }else{
    g=gen();
  }

  function next(data){
    var tmp=g.next(data);
    if(tmp.done){
      return ;
    }else{
      tmp.value.then(next);
    }
  }

  next();
}

函數接收一個generator,並讓其中的非同步能夠自動執行。使用這個run函數,我們來讓上一個非同步代碼自動執行:

var fs = require('fs');

var run = function(gen) {
  var g;
  if (typeof gen.next === 'function') {
    g = gen;
  } else {
    g = gen();
  }

  function next(data) {
    var tmp = g.next(data);
    if (tmp.done) {
      return;
    } else {
      tmp.value.then(next);
    }
  }

  next();
}

var readFile = function(fileName) {
  return new Promise(function(resolve, reject) {
    fs.readFile(fileName, function(err, data) {
      if (err) {
        reject(err);
      } else {
        resolve(data);
      }
    })
  })
}

//將讀文件的過程放在generator中
var gen = function*() {
  var data = yield readFile('./file1');
  console.log(data.toString());
  data = yield readFile('./file2');
  console.log(data.toString());
}
//下麵只需要將gen放入run當中即可自動執行
run(gen);

執行上述代碼,即可看到終端依次列印出了file1和file2的內容。

需要指出的是,這裡的run函數為了簡單起見只支持promise,而實際的co函數還支持thunk等。

這樣一來,co函數的兩大功能基本就完整介紹了,一個是洋蔥模型的流程式控制制,另一個是非同步同步化代碼的自動執行。在下一篇文章中,我將帶大家對這兩個功能進行整合,寫出我們自己的一個co函數!

這篇文章的代碼同樣可以在github上面找到:https://github.com/mly-zju/async-js-demo,其中promise_generator.js就是本篇的示例源碼。

同樣歡迎大家多多關註我的github pages個人博客哦,會不定期更新我的技術文章~


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • js元素綁定事件 想給一個元素綁定一個方法之後,在綁定一個方法而且不被覆蓋 window.onload = function () { alert('a'); } window.onlaod=function(){ alert('b') } 我們發現它只彈出一個,有一個被覆蓋了 下麵就說一下js中的 ...
  • 在JavaScript中,RegExp對象表示正則表達式,用來對字元串進行匹配。 一、兩種定義方法: 1、直接量法: /pattern/attribute 2、對象法: new RegExp(pattern,attribute); 參數說明: pattern是一個字元串,指定了正則表達式的模式或其他 ...
  • 一:child_process child_process是NodeJs的重要模塊。幫助我們創建多進程任務,更好的利用了電腦的多核性能。 當然也支持線程間的通信。 二:child_process的幾個API 非同步: child_process.exec(command[, options][, c ...
  • 認識JavaScript中的變數 JavaScript中的變數有兩種類型,一種是基本類型、一種是引用類型。 + 基本數據類型:Defined,Null,Boolean,Number,String。註意String是屬於基本數據類型,這不同於其他高級語言。 + 引用類型即對象,包括內置對象和自定義對象 ...
  • 恢復內容開始 樣式表: 一。內聯:寫在body中 直接在內容的標簽中添加style屬性:例如<div style="...">1234</div> 二。內嵌:寫在head中 一般寫在標題標簽的後面,添加<style type="text/css"></style>標簽,p為需要改變的標簽 三。外部樣 ...
  • 給select綁定ng-model時,經常會出現首選第一個option值是undefined。如圖, 源代碼 主要原因是:ng-model綁定的值“newSupplier.companytype”沒有初始值。瀏覽器自動解析為undefined。 瀏覽器解析代碼: 解決辦法: 只要在js文件中,初始化 ...
  • var arr = [4, 0, 7, 9, 0, 0, 2, 6, 0, 3, 1, 0];要求將數組中的0項去掉,將不為0的值存入一個新的數組,生成新的數組 <!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <tit ...
  • <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> <style type="text/css"> ul, li { margin: 0; padding: 0; list-style-type: none; } ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...