Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nodejs实现bigPipe #22

Open
damoclesX opened this issue Dec 21, 2015 · 0 comments
Open

nodejs实现bigPipe #22

damoclesX opened this issue Dec 21, 2015 · 0 comments

Comments

@damoclesX
Copy link
Owner

引言

在页面需要渲染大量内容,并且这些内容所需的数据不能够一步获取,可以采用bigpipe进行性能优化

如某页面需要显示以下信息:

  • 用户基本信息(姓名、年龄、联系方式)
  • 用户的好友列表
  • 用户的好友的最新动态
  • 推荐的游戏
  • 推荐的功能
  • 推荐的好友
  • 更多信息

页面显示这些信息的时候,由于这些信息所需的数据来自数据库中的多张表,或者来自第三方。如果等 所有数据都获取完毕再呈现页面,用户会感觉“有点慢”。如果获取到一部分数据、渲染了一部分页面就呈现,用户会感觉“快了一点”。这其实就是bigpipe,后端拿到一个模块的完整数据就先返回它(如:用户的基本信息)。

代码实现

使用express搭建服务器

var express = require('express');
var app = express();

app.get('/' ,(req, res) => {
    res.write(`
        <!doctype html>
        <html>
            <head>
                <meta charset="utf-8" />
                <title>bigpipedemo</title>
                <script>
                    function bigpipe(content, container){
                        document.getElementById(container).innerHTML = content;
                    }
                </script>
            </head>
            <body>
                <div id="header"></div>
                <div id="body"></div>
                <div id="footer"></div>
    `);//返回网页骨架,及客户端bigpipe调用函数

    //使用在获取内容后,返回相应内容块
    getHeaderContent().then( (content) => {
        res.write(bigpipe(content, 'header'))
    })
    getBodyContent().then( (content) => {
        res.write(bigpipe(content, 'body'))
    })
    getFooterContent().then( (content) => {
        res.write(bigpipe(content, 'footer'))
        res.end('</body></html>')
    })
})

//服务器端bigpipe函数,组装一下数据,返回客户端bigpipe函数的调用
function bigpipe(html, container){
    return `<script>bigpipe('${html}', '${container}')</script>`;
}

/*
都返回一个promise对象
*/
//模拟获取header内容
function getHeaderContent(){
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('<h1>这是header的内容</h1>')
        }, 1000)
    });
}
//模拟获取body内容
function getBodyContent(){
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('<h1>这是body的内容</h1>')
        }, 2000)
    });
}
//模拟获取footer内容
function getFooterContent(){
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('<h1>这是footer的内容</h1>')
        }, 3000)
    });
}
app.listen(80)

这里假设是获取footer的时间最长,所有在返回footer内容后,结束这次请求。但现实情况是,获取内容的时间未知,返回内容的情况未知。这样的话,上面的代码便会有问题。解决思路:由于获取内容的数量是已知的(例子是3个:header、body、footer),那在每一次返回内容后,调用一个函数,判断已经获取内容的数量是不是已经是3了,如果是则结束请求,返回最后的内容。这里使用events模块来实现

var express = require('express');
var EventEmiter = require('events');
var app = express();

app.get('/' ,(req, res) => {

    var emiter = new EventEmiter();
    //自定义要监听的属性 --属性名还可以有进一步优化
    emiter._length = 0;
    //监听自定义的flush事件
    emiter.on('flush', () => {
        //当监听的属性是获取内容的数量时,结束请求,返回最后内容
        if(emiter._length == 3){
            res.end('</body></html>')
        }
    })

    res.write(`
        <!doctype html>
        <html>
            <head>
                <meta charset="utf-8" />
                <title>bigpipedemo</title>
                <script>
                    function bigpipe(content, container){
                        document.getElementById(container).innerHTML = content;
                    }
                </script>
            </head>
            <body>
                <div id="header"></div>
                <div id="body"></div>
                <div id="footer"></div>
            </body>
        </html>
    `);
    getHeaderContent().then( (content) => {
        res.write(bigpipe(content, 'header'))
        touch(emiter)
    })
    getBodyContent().then( (content) => {
        res.write(bigpipe(content, 'body'))
        touch(emiter)
    })
    getFooterContent().then( (content) => {
        res.write(bigpipe(content, 'footer'))
        touch(emiter)
    })
})
//改变之前设置好的属性,触发flush事件
function touch(emiter){
    emiter._length++;
    emiter.emit('flush');
}
function bigpipe(html, container){
    return `<script>bigpipe('${html}', '${container}')</script>`;
}
function getHeaderContent(){
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('<h1>这是header的内容</h1>')
        }, 1000)
    });
}
function getBodyContent(){
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('<h1>这是body的内容</h1>')
        }, 2000)
    });
}
function getFooterContent(){
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('<h1>这是footer的内容</h1>')
        }, 3000)
    });
}
app.listen(80)

这样无论谁先返回,都不会影响其他,当返回了最后一个内容时,结束请求。简单实现就是这样,还可以继续深入...

参考链接

http://taobaofed.org/blog/2015/12/17/seller-bigpipe/
http://sjtutmz.blog.163.com/blog/static/98888660201191295614789/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant