欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

node.js實(shí)現(xiàn)BigPipe詳解

 更新時(shí)間:2014年12月05日 12:12:53   投稿:junjie  
這篇文章主要介紹了node.js實(shí)現(xiàn)BigPipe詳解,BigPipe是 Facebook 開發(fā)的優(yōu)化網(wǎng)頁加載速度的技術(shù),BigPipe 的核心概念就是只用一個(gè) HTTP 請(qǐng)求,只是頁面元素不按順序發(fā)送而已,需要的朋友可以參考下

BigPipe 是 Facebook 開發(fā)的優(yōu)化網(wǎng)頁加載速度的技術(shù)。網(wǎng)上幾乎沒有用 node.js 實(shí)現(xiàn)的文章,實(shí)際上,不止于 node.js,BigPipe 用其他語言的實(shí)現(xiàn)在網(wǎng)上都很少見。以至于這技術(shù)出現(xiàn)很久以后,我還以為就是整個(gè)網(wǎng)頁的框架先發(fā)送完畢后,用另一個(gè)或幾個(gè) ajax 請(qǐng)求再請(qǐng)求頁面內(nèi)的模塊。直到不久前,我才了解到原來 BigPipe 的核心概念就是只用一個(gè) HTTP 請(qǐng)求,只是頁面元素不按順序發(fā)送而已。

了解了這個(gè)核心概念就好辦了,得益于 node.js 的異步特性,很容易就可以用 node.js 實(shí)現(xiàn) BigPipe。本文會(huì)一步一步詳盡地用例子來說明 BigPipe 技術(shù)的起因和一個(gè)基于 node.js 的簡單實(shí)現(xiàn)。

我會(huì)用 express 來演示,簡單起見,我們選用 jade 作為模版引擎,并且我們不使用引擎的子模版(partial)特性,而是以子模版渲染完成以后的 HTML 作為父模版的數(shù)據(jù)。

先建一個(gè) nodejs-bigpipe 的文件夾,寫一個(gè) package.json 文件如下:

復(fù)制代碼 代碼如下:

{
    "name": "bigpipe-experiment"
  , "version": "0.1.0"
  , "private": true
  , "dependencies": {
        "express": "3.x.x"
      , "consolidate": "latest"
      , "jade": "latest"
    }
}

運(yùn)行 npm install 安裝這三個(gè)庫,consolidate 是用來方便調(diào)用 jade 的。

先做個(gè)最簡單的嘗試,兩個(gè)文件:

app.js:

復(fù)制代碼 代碼如下:

var express = require('express')
  , cons = require('consolidate')
  , jade = require('jade')
  , path = require('path')

var app = express()

app.engine('jade', cons.jade)
app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'jade')

app.use(function (req, res) {
  res.render('layout', {
      s1: "Hello, I'm the first section."
    , s2: "Hello, I'm the second section."
  })
})

app.listen(3000)

views/layout.jade

復(fù)制代碼 代碼如下:

doctype html

head
  title Hello, World!
  style
    section {
      margin: 20px auto;
      border: 1px dotted gray;
      width: 80%;
      height: 150px;
    }

section#s1!=s1
section#s2!=s2

效果如下:

接下來我們把兩個(gè) section 模版放到兩個(gè)不同的模版文件里:

views/s1.jade:

復(fù)制代碼 代碼如下:

h1 Partial 1
.content!=content

views/s2.jade:

復(fù)制代碼 代碼如下:

h1 Partial 2
.content!=content

在 layout.jade 的 style 里增加一些樣式

復(fù)制代碼 代碼如下:

section h1 {
  font-size: 1.5;
  padding: 10px 20px;
  margin: 0;
  border-bottom: 1px dotted gray;
}
section div {
  margin: 10px;
}

將 app.js 的 app.use() 部分更改為:

復(fù)制代碼 代碼如下:

var temp = {
    s1: jade.compile(fs.readFileSync(path.join(__dirname, 'views', 's1.jade')))
  , s2: jade.compile(fs.readFileSync(path.join(__dirname, 'views', 's2.jade')))
}
app.use(function (req, res) {
  res.render('layout', {
      s1: temp.s1({ content: "Hello, I'm the first section." })
    , s2: temp.s2({ content: "Hello, I'm the second section." })
  })
})

之前我們說“以子模版渲染完成以后的 HTML 作為父模版的數(shù)據(jù)”,指的就是這樣,temp.s1 和 temp.s2 兩個(gè)方法會(huì)生成 s1.jade 和 s2.jade 兩個(gè)文件的 HTML 代碼,然后把這兩段代碼作為 layout.jade 里面 s1、s2 兩個(gè)變量的值。

現(xiàn)在頁面看起來是這樣子:

一般來說,兩個(gè) section 的數(shù)據(jù)是分別獲取的——不管是通過查詢數(shù)據(jù)庫還是 RESTful 請(qǐng)求,我們用兩個(gè)函數(shù)來模擬這樣的異步操作。

復(fù)制代碼 代碼如下:

var getData = {
    d1: function (fn) {
        setTimeout(fn, 3000, null, { content: "Hello, I'm the first section." })
    }
  , d2: function (fn) {
        setTimeout(fn, 5000, null, { content: "Hello, I'm the second section." })
    }
}

這樣一來,app.use() 里的邏輯就會(huì)比較復(fù)雜了,最簡單的處理方式是:

復(fù)制代碼 代碼如下:

app.use(function (req, res) {
  getData.d1(function (err, s1data) {
    getData.d2(function (err, s2data) {
      res.render('layout', {
          s1: temp.s1(s1data)
        , s2: temp.s2(s2data)
      })
    })
  })
})

這樣也可以得到我們想要的結(jié)果,但是這樣的話,要足足 8 秒才會(huì)返回。

其實(shí)實(shí)現(xiàn)邏輯可以看出 getData.d2 是在 getData.d1 的結(jié)果返回后才開始調(diào)用,而它們兩者并沒有這樣的依賴關(guān)系。我們可以用如 async 之類的處理 JavaScript 異步調(diào)用的庫來解決這樣的問題,不過我們這里就簡單手寫吧:

復(fù)制代碼 代碼如下:

app.use(function (req, res) {
  var n = 2
    , result = {}
  getData.d1(function (err, s1data) {
    result.s1data = s1data
    --n || writeResult()
  })
  getData.d2(function (err, s2data) {
    result.s2data = s2data
    --n || writeResult()
  })
  function writeResult() {
    res.render('layout', {
        s1: temp.s1(result.s1data)
      , s2: temp.s2(result.s2data)
    })
  }
})

這樣就只需 5 秒。

在接下來的優(yōu)化之前,我們加入 jquery 庫并把 css 樣式放到外部文件,順便,把之后我們會(huì)用到的瀏覽器端使用 jade 模板所需要的 runtime.js 文件也加入進(jìn)來,在包含 app.js 的目錄下運(yùn)行:

復(fù)制代碼 代碼如下:

mkdir static
cd static
curl http://code.jquery.com/jquery-1.8.3.min.js -o jquery.js
ln -s ../node_modules/jade/runtime.min.js jade.js

并且把 layout.jade 中的 style 標(biāo)簽里的代碼拿出來放到 static/style.css 里,然后把 head 標(biāo)簽改為:

復(fù)制代碼 代碼如下:

head
  title Hello, World!
  link(href="/static/style.css", rel="stylesheet")
  script(src="/static/jquery.js")
  script(src="/static/jade.js")

在 app.js 里,我們把它們兩者的下載速度都模擬為兩秒,在app.use(function (req, res) {之前加入:

復(fù)制代碼 代碼如下:

var static = express.static(path.join(__dirname, 'static'))
app.use('/static', function (req, res, next) {
  setTimeout(static, 2000, req, res, next)
})

受外部靜態(tài)文件的影響,我們的頁面現(xiàn)在的加載時(shí)間為 7 秒左右。

如果我們一收到 HTTP 請(qǐng)求就把 head 部分返回,然后兩個(gè) section 等到異步操作結(jié)束后再返回,這是利用了 HTTP 的分塊傳輸編碼機(jī)制。在 node.js 里面只要使用 res.write() 方法就會(huì)自動(dòng)加上 Transfer-Encoding: chunked 這個(gè) header 了。這樣就能在瀏覽器加載靜態(tài)文件的同時(shí),node 服務(wù)器這邊等待異步調(diào)用的結(jié)果了,我們先刪除 layout.jade 中的這 section 這兩行:

復(fù)制代碼 代碼如下:

section#s1!=s1
section#s2!=s2

因此我們?cè)?res.render() 里也不用給 { s1: …, s2: … } 這個(gè)對(duì)象,并且因?yàn)?res.render() 默認(rèn)會(huì)調(diào)用 res.end(),我們需要手動(dòng)設(shè)置 render 完成后的回調(diào)函數(shù),在里面用 res.write() 方法。layout.jade 的內(nèi)容也不必在 writeResult() 這個(gè)回調(diào)函數(shù)里面,我們可以在收到這個(gè)請(qǐng)求時(shí)就返回,注意我們手動(dòng)添加了 content-type 這個(gè) header:

復(fù)制代碼 代碼如下:

app.use(function (req, res) {
  res.render('layout', function (err, str) {
    if (err) return res.req.next(err)
    res.setHeader('content-type', 'text/html; charset=utf-8')
    res.write(str)
  })
  var n = 2
  getData.d1(function (err, s1data) {
    res.write('<section id="s1">' + temp.s1(s1data) + '</section>')
    --n || res.end()
  })
  getData.d2(function (err, s2data) {
    res.write('<section id="s2">' + temp.s2(s2data) + '</section>')
    --n || res.end()
  })
})

現(xiàn)在最終加載速度又回到大概 5 秒左右了。實(shí)際運(yùn)行中瀏覽器先收到 head 部分代碼,就去加載三個(gè)靜態(tài)文件,這需要兩秒時(shí)間,然后到第三秒,出現(xiàn) Partial 1 部分,第 5 秒出現(xiàn) Partial 2 部分,網(wǎng)頁加載結(jié)束。就不給截圖了,截圖效果和前面 5 秒的截圖一樣。

但是要注意能實(shí)現(xiàn)這個(gè)效果是因?yàn)?getData.d1 比 getData.d2 快,也就是說,先返回網(wǎng)頁中的哪個(gè)區(qū)塊取決于背后的接口異步調(diào)用結(jié)果誰先返回,如果我們把 getData.d1 改成 8 秒返回,那就會(huì)先返回 Partial 2 部分,s1 和 s2 的順序?qū)φ{(diào),最終網(wǎng)頁的結(jié)果就和我們的預(yù)期不符了。

這個(gè)問題最終將我們引導(dǎo)到 BigPipe 上來,BigPipe 就是能讓網(wǎng)頁各部分的顯示順序與數(shù)據(jù)的傳輸順序解耦的技術(shù)。

其基本思路就是,首先傳輸整個(gè)網(wǎng)頁大體的框架,需要稍后傳輸?shù)牟糠钟每?div(或其他標(biāo)簽)表示:

復(fù)制代碼 代碼如下:

res.render('layout', function (err, str) {
  if (err) return res.req.next(err)
  res.setHeader('content-type', 'text/html; charset=utf-8')
  res.write(str)
  res.write('<section id="s1"></section><section id="s2"></section>')
})

然后將返回的數(shù)據(jù)用 JavaScript 寫入

復(fù)制代碼 代碼如下:

getData.d1(function (err, s1data) {
  res.write('<script>$("#s1").html("' + temp.s1(s1data).replace(/"/g, '\\"') + '")</script>')
  --n || res.end()
})

s2 的處理與此類似。這時(shí)你會(huì)看到,請(qǐng)求網(wǎng)頁的第二秒,出現(xiàn)兩個(gè)空白虛線框,第五秒,出現(xiàn) Partial 2 部分,第八秒,出現(xiàn) Partial 1 部分,網(wǎng)頁請(qǐng)求完成。

至此,我們就完成了一個(gè)最簡單的 BigPipe 技術(shù)實(shí)現(xiàn)的網(wǎng)頁。

需要注意的是,要寫入的網(wǎng)頁片段有 script 標(biāo)簽的情況,如將 s1.jade 改為:

復(fù)制代碼 代碼如下:

h1 Partial 1
.content!=content
script
  alert("alert from s1.jade")

然后刷新網(wǎng)頁,會(huì)發(fā)現(xiàn)這句 alert 沒有執(zhí)行,而且網(wǎng)頁會(huì)有錯(cuò)誤。查看源代碼,知道是因?yàn)?<script> 里面的字符串出現(xiàn) </script> 而導(dǎo)致的錯(cuò)誤,只要將其替換為 <\/script> 即可

復(fù)制代碼 代碼如下:

res.write('<script>$("#s1").html("' + temp.s1(s1data).replace(/"/g, '\\"').replace(/<\/script>/g, '<\\/script>') + '")</script>')

以上我們便說明了 BigPipe 的原理和用 node.js 實(shí)現(xiàn) BigPipe 的基本方法。而在實(shí)際中應(yīng)該怎樣運(yùn)用呢?下面提供一個(gè)簡單的方法,僅供拋磚引玉,代碼如下:

復(fù)制代碼 代碼如下:

var resProto = require('express/lib/response')
resProto.pipe = function (selector, html, replace) {
  this.write('<script>' + '$("' + selector + '").' +
    (replace === true ? 'replaceWith' : 'html') +
    '("' + html.replace(/"/g, '\\"').replace(/<\/script>/g, '<\\/script>') +
    '")</script>')
}
function PipeName (res, name) {
  res.pipeCount = res.pipeCount || 0
  res.pipeMap = res.pipeMap || {}
  if (res.pipeMap[name]) return
  res.pipeCount++
  res.pipeMap[name] = this.id = ['pipe', Math.random().toString().substring(2), (new Date()).valueOf()].join('_')
  this.res = res
  this.name = name
}
resProto.pipeName = function (name) {
  return new PipeName(this, name)
}
resProto.pipeLayout = function (view, options) {
  var res = this
  Object.keys(options).forEach(function (key) {
    if (options[key] instanceof PipeName) options[key] = '<span id="' + options[key].id + '"></span>'
  })
  res.render(view, options, function (err, str) {
    if (err) return res.req.next(err)
    res.setHeader('content-type', 'text/html; charset=utf-8')
    res.write(str)
    if (!res.pipeCount) res.end()
  })
}
resProto.pipePartial = function (name, view, options) {
  var res = this
  res.render(view, options, function (err, str) {
    if (err) return res.req.next(err)
    res.pipe('#'+res.pipeMap[name], str, true)
    --res.pipeCount || res.end()
  })
}
app.get('/', function (req, res) {
  res.pipeLayout('layout', {
      s1: res.pipeName('s1name')
    , s2: res.pipeName('s2name')
  })
  getData.d1(function (err, s1data) {
    res.pipePartial('s1name', 's1', s1data)
  })
  getData.d2(function (err, s2data) {
    res.pipePartial('s2name', 's2', s2data)
  })
})

還要在 layout.jade 把兩個(gè) section 添加回來:

復(fù)制代碼 代碼如下:

section#s1!=s1
section#s2!=s2

這里的思路是,需要 pipe 的內(nèi)容先用一個(gè) span 標(biāo)簽占位,異步獲取數(shù)據(jù)并渲染完成相應(yīng)的 HTML 代碼后再輸出給瀏覽器,用 jQuery 的 replaceWith 方法把占位的 span 元素替換掉。

本文的代碼在 https://github.com/undozen/bigpipe-on-node ,我把每一步做成一個(gè) commit 了,希望你 clone 到本地實(shí)際運(yùn)行并 hack 一下看看。因?yàn)楹竺鎺撞缴婕暗郊虞d順序了,確實(shí)要自己打開瀏覽器才能體驗(yàn)到而無法從截圖上看到(其實(shí)應(yīng)該可以用 gif 動(dòng)畫實(shí)現(xiàn),但是我懶得做了)。

關(guān)于 BigPipe 的實(shí)踐還有很大的優(yōu)化空間,比如說,要 pipe 的內(nèi)容最好設(shè)置一個(gè)觸發(fā)的時(shí)間值,如果異步調(diào)用的數(shù)據(jù)很快返回,就不需要用 BigPipe,直接生成網(wǎng)頁送出即可,可以等到數(shù)據(jù)請(qǐng)求超過一定時(shí)間才用 BigPipe。使用 BigPipe 相比 ajax 既節(jié)省了瀏覽器到 node.js 服務(wù)器的請(qǐng)求數(shù),又節(jié)省了 node.js 服務(wù)器到數(shù)據(jù)源的請(qǐng)求數(shù)。不過具體的優(yōu)化和實(shí)踐方法,等到雪球網(wǎng)用上 BigPipe 以后再分享吧。

相關(guān)文章

  • node使用Git Bash作為命令行終端示例

    node使用Git Bash作為命令行終端示例

    這篇文章主要為大家介紹了node使用Git Bash作為命令行終端示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-08-08
  • Nodejs基于LRU算法實(shí)現(xiàn)的緩存處理操作示例

    Nodejs基于LRU算法實(shí)現(xiàn)的緩存處理操作示例

    這篇文章主要介紹了Nodejs基于LRU算法實(shí)現(xiàn)的緩存處理操作,結(jié)合具體實(shí)例形式分析了LRU算法的原理、功能以及nodejs使用LRU算法實(shí)現(xiàn)緩存處理操作的相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下
    2017-03-03
  • NodeJs下的測(cè)試框架Mocha的簡單介紹

    NodeJs下的測(cè)試框架Mocha的簡單介紹

    本篇文章主要介紹了NodeJs下的測(cè)試框架Mocha的簡單介紹,是目前最為流行的javascript框架之一,在本文我們重點(diǎn)介紹它在NodeJs上的使用。有興趣的可以了解一下。
    2017-02-02
  • node+koa+canvas繪制出貨單、收據(jù)票據(jù)的方法

    node+koa+canvas繪制出貨單、收據(jù)票據(jù)的方法

    在生成票據(jù)需求中,我們會(huì)想到前端生成或者后端生成返回圖片地址訪問兩個(gè)方法,前端生成則不需要調(diào)用接口,而后端是在完成整個(gè)流程時(shí)就進(jìn)行生成然后把上傳的地址保存數(shù)據(jù)庫,這篇文章主要介紹了node+koa+canvas繪制出貨單,收據(jù),票據(jù),需要的朋友可以參考下
    2022-09-09
  • Node.JS用純JavaScript生成圖片或滑塊式驗(yàn)證碼功能

    Node.JS用純JavaScript生成圖片或滑塊式驗(yàn)證碼功能

    有一些Node.JS圖片生成類庫,比如node-captcha等的類庫,需要c/c++程序生成圖片??缙脚_(tái)部署不是很方便。這里介紹幾個(gè)用純JS實(shí)現(xiàn)的圖片驗(yàn)證碼生成模塊,需要的朋友可以參考下
    2019-09-09
  • Google官方支持的NodeJS訪問API,提供后臺(tái)登錄授權(quán)

    Google官方支持的NodeJS訪問API,提供后臺(tái)登錄授權(quán)

    Google官方支持的NodeJS集成客戶端,用以訪問Google APIs, 支持OAuth 2.0授信及登錄認(rèn)證。登錄以后即可在后臺(tái)訪問例如 Google Drive(云存儲(chǔ)), Google Analytics, Gmail等服務(wù)。
    2014-07-07
  • Node.js中的require.resolve方法使用簡介

    Node.js中的require.resolve方法使用簡介

    在Node.js中,可以使用require.resolve函數(shù)來查詢某個(gè)模塊文件的帶有完整絕對(duì)路徑的文件名,下面這篇文章主要介紹了Node.js中require.resolve方法使用的相關(guān)資料,需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-04-04
  • Node處理CPU密集型任務(wù)有哪些方法

    Node處理CPU密集型任務(wù)有哪些方法

    這篇文章主要介紹了Node處理CPU密集型任務(wù)有哪些方法,Node是一個(gè)非阻塞I/O和事件驅(qū)動(dòng)的JavaScript運(yùn)行環(huán)境,所以它非常適合用來構(gòu)建I/O密集型應(yīng)用,例如Web服務(wù)等
    2022-09-09
  • 解決修復(fù)npm安裝全局模塊權(quán)限的問題

    解決修復(fù)npm安裝全局模塊權(quán)限的問題

    今天小編就為大家分享一篇解決修復(fù)npm安裝全局模塊權(quán)限的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2018-05-05
  • node.js中格式化數(shù)字增加千位符的幾種方法

    node.js中格式化數(shù)字增加千位符的幾種方法

    這篇文章主要介紹了node.js中格式化數(shù)字增加千位符的幾種方法,本文給出3種實(shí)現(xiàn)方法,并分別給出實(shí)例代碼,需要的朋友可以參考下
    2015-07-07

最新評(píng)論