• 进击Node.js基础(二)


    一.一个牛逼闪闪的知识点Promise

      npm install bluebird

    二.Promise实例

    ball.html

    <!doctype>
    <!DOCTYPE html>
    <html>
    <head>
        <title>Promise animation</title>
        <style>
            .ball {
                width: 40px;
                height: 40px;
                border-radius: 20px;
            }
            .ball1 {
                background: red;
            }
            .ball2 {
                background: yellow;
            }
            .ball3 {
                background: green;
            }
        </style>
        <script src="./node_modules/bluebird/js/browser/bluebird.js"></script>
    </head>
    <body>
        <div class="ball ball1" style="margin-left: 0;"></div>
        <div class="ball ball2" style="margin-left: 0;"></div>
        <div class="ball ball3" style="margin-left: 0;"></div>
    
        <script type="text/javascript">
            var ball1 = document.querySelector('.ball1')
            var ball2 = document.querySelector('.ball2')
            var ball3 = document.querySelector('.ball3')
    
            function animate(ball, distance, cb) {
                setTimeout(function() {
                    var marginLeft = parseInt(ball.style.marginLeft, 10)
                    if(marginLeft === distance) {
                        cb && cb()
                    }else {
                        if(marginLeft < distance) {
                            marginLeft++
                        }else {
                            marginLeft--
                        }
                        ball.style.marginLeft = marginLeft
                        animate(ball, distance, cb)
                    }
                }, 13)
            }
    
            animate(ball1, 100, function() {
                animate(ball2, 200, function() {
                    animate(ball3, 300, function(){
                        animate(ball3, 150, function(){
                            animate(ball2, 150, function(){
                                animate(ball1, 150, function(){
                                    //
                                })
                            })
                        })
                    })
                })
            })
        </script>
    </body>
    </html>

    使用Promise改造后,ball2.html如下:

    <!doctype>
    <!DOCTYPE html>
    <html>
    <head>
        <title>Promise animation</title>
        <style>
            .ball {
                width: 40px;
                height: 40px;
                border-radius: 20px;
            }
            .ball1 {
                background: red;
            }
            .ball2 {
                background: yellow;
            }
            .ball3 {
                background: green;
            }
        </style>
    </head>
    <body>
        <div class="ball ball1" style="margin-left: 0;"></div>
        <div class="ball ball2" style="margin-left: 0;"></div>
        <div class="ball ball3" style="margin-left: 0;"></div>
    
        <script type="text/javascript">
            var ball1 = document.querySelector('.ball1')
            var ball2 = document.querySelector('.ball2')
            var ball3 = document.querySelector('.ball3')
    
            var Promise = window.Promise
    
            function promiseAnimate(ball, distance) {
                return new Promise(function(resolve, reject) {
    
                    function _animate() {
                        setTimeout(function() {
                            var marginLeft = parseInt(ball.style.marginLeft, 10)
                            if(marginLeft === distance) {
                                resolve
                            }else {
                                if(marginLeft < distance) {
                                    marginLeft++
                                }else {
                                    marginLeft--
                                }
                                ball.style.marginLeft = marginLeft + 'px'
                                _animate()
                            }
                        }, 13)
                    }
    
                    _animate()
                })
            }
    
            promiseAnimate(ball1, 100)
                .then(function() {
                    return promiseAnimate(ball2, 200)
                })
                .then(function() {
                    return promiseAnimate(ball3, 300)
                })
                .then(function() {
                    return promiseAnimate(ball3, 150)
                })
                .then(function() {
                    return promiseAnimate(ball2, 150)
                })
                .then(function() {
                    return promiseAnimate(ball1, 150)
                })
        </script>
    </body>
    </html>

    运行效果:

    三.牛逼闪闪的Promise只需三点
    1.Promise是JS针对异步操作场景的解决方案
      针对异步的场景,业界有很多解决方案,如回调、事件机制
      Promise是一个对象,同时它也一种规范,针对异步操作约定了统一的接口,表示一个异步操作的最终结果以同步的方式来写代码,执行的操作是异步的,但是又保证程序的执行顺序是同步的。
      原本是社区的一个规范的构想,被加入到ES6的语言标准里面,比如Chrom,Firefox浏览器已对它进行了实现。
    2.Promise对象三种状态
      未完成(pending)
      已完成(fulfilled)
      失败(rejected)
      只能从未完成到已完成,或者从未完成到失败,不可逆的。
    3.ES6的Promise语言标准、Promise/A+规范
      Promise/A+规范算是之前的Promise/A规范的升级版,规定了行为标准,扩展了原来的规范,并且覆盖了一些约定俗成的行为,总之,它就是一个更为标准的Promise能力和使用的规范。
      Promise/A与A+不同点
      a.A+规范通过术语thenable来区分promise对象
      b.A+定义onFulfilled/onRejected必须是作为函数来调用,而且调用过程必须是异步的
      c.A+严格定义了then方式链式调用时onFulfilled/onRejected的调用顺序
    4.如何使用Promise then方法

    promiseObj.then(onFulfilled, onRejected)
    onFulfilled = function(value) {
      return promiseObj2
    }
    onRejected = function(err) {}

      Promise会保证then方法回调的顺序,依次调用,会等前面的promise方法执行完成后才会执行后面then中的方法。

      Promise最大的特点是:回调函数可以写成规范的链式的写法,程序流程可以很清楚。它的一整套接口可以实现很强大的功能,比如为多个异步操作部署一个回调函数,为多个回调函数中抛出的错误统一指定处理的方法等等。
    5.在什么场景下使用
      只要是异步编程的地方,都可以使用Promise,但不能Promise而Promise,如业务场景是很简单的异步操作,维护很容易了,没必要引入一个Promise库来重构。
    6.Promise库
      Promise库市面上有不少,如下所示:
      bluebird
      Q
      then.js
      es6-promise
      ypromise
      async
      nativeo-promise-only

    四.重写爬虫,更优雅的异步编程
      https协议是基于ssl/tls的http协议,所有数据都是在ssl/tls协议的封装之上传输的。

      也就是说,https协议是在http协议的基础之上添加了ssl/tls握手及数据加密传输,因此,ssl/tls协议就是http和https最大区别。

      在搭建https服务器的时候,需要有一个ssl证书。

    https.js

    var https = require('https')
    var fs = require('fs')
    
    var options = {
        key: fs.readFileSync('ssh_key.pem'),
        cert: fs.readFileSync('ssh_cert.pem')
    }
    
    https.createServer(options, function(req, res) {
        res.writeHead(200)
        res.end('Hello Imooc')
    }).listen(8090)

    重写爬虫,更优雅的异步编程,promise_crawler.js

    var http = require('http')
    var Promise = require('bluebird')
    var cheerio = require('cheerio')
    var baseUrl = 'http://www.imooc.com/learn/'
    var url = 'http://www.imooc.com/learn/348'
    var videoIds = [348, 259, 197, 134, 75]
    
    function filterChapters(html) {
        var $ = cheerio.load(html)
    
        var chapters = $('.learnchapter')
    
        var title = $('#page_header .path span').text()
        var number = parseInt($($('.info_num i')[0]).text().trim(), 10)
        // var courseData = {
        //     title: title,
        //     number: number,
        //     videos: [{
        //         chapterTitle: '',
        //         videos: [
        //             title: '',
        //             id: ''
        //         ]
        //     }]
        // }
    
        var courseData = {
            title: title,
            number: number,
            videos: []
        }
    
        chapters.each(function(item) {
            var chapter = $(this)
            var charterTitle = chapter.find('strong').text()
            var videos = chapter.find('.video').children('li')
            var chapterData = {
                chapterTitle: chapterTitle,
                videos : []
            }
    
            videos.each(function(item) {
                var video = $(this).find('.studyvideo')
                var videoTitle = video.text()
                var id = video.attr('href').split('video/')[1]
    
                chapterData.videos.push({
                    title: videoTitle,
                    id: id
                })
            })
    
            courseData.videos.push(chapterData)
        })
        return courseData;
    }
    
    function printCourseInfo(coursesData) {
        coursesData.forEach(function(courseData) {
            console.log(courseData.number + '  人学过 ' + courseData.title + '
    ')
        })
        coursesData.forEach(function(courseData) {
            console.log('###' + courseData.title + '
    ')
            courseData.videos.forEach(function(item) {
                var chapterTitle = item.chapterTitle
    
                console.log(charterTitle + '
    ')
    
                item.videos.forEach(function(video) {
                    console.log(' [' + video.id + ']' + video.title  + '
    ')
                })
            })
        })
    }
    
    function getPageAsync(url) {
        return new Promise(function(resolve, reject) {
            console.log('正在爬取' + url)
    
            http.get(url, function(res) {
                var html = ''
                res.on('data', function(data) {
                    html += data
                })
    
                res.on('end', function() {
                    resolve(html)
                })
            }).on('error', function(e) {
                reject(e)
                console.log('获取课程数据出错!')
            })
        })
    }
    
    var fetchCourseArray = []
    videoIds.forEach(function(id) {
        fetchCourseArray.push(getPageAsync(baseUrl + id))
    })
    
    Promise
        .all(fetchCourseArray)
        .then(function(pages) {
            var coursesData = []
    
            pages.forEach(function(html) {
                var courses = filterChapters(html)
                coursesData.push(courses)
            })
    
            coursesData.sort(function(a, b) {
                return a.number < b.number
            })
    
            printCourseInfo(coursesData)
        })

    运行结果:

    五.Nodejs API
    1.横扫Nodejs API-Buffer的实例
      Buffer:用来处理二进制的数据。因为JS字符串是UTF-8的编码存储的,处理二进制的能力是很弱的,而网络层对于不同资源的请求、响应,甚至是文件都是二进制的方式来交互的,所以Node.js有这么一个接口,专门用于存放二进制数据的缓存区,并提供了一些方法来对缓存区数据进行进一步的处理。Buffer在Node.js中是要以全局访问的,不需要用require来加载。

    > Buffer
    { [Function: Buffer]
    poolSize: 8192,
    from: [Function: from],
    of: [Function: of],
    alloc: [Function: alloc],
    allocUnsafe: [Function: allocUnsafe],
    allocUnsafeSlow: [Function: allocUnsafeSlow],
    isBuffer: [Function: isBuffer],
    compare: [Function: compare],
    isEncoding: [Function: isEncoding],
    concat: [Function: concat],
    byteLength: [Function: byteLength],
    [Symbol(kIsEncodingSymbol)]: [Function: isEncoding] }
    > new Buffer('Hello Node.js')
    <Buffer 48 65 6c 6c 6f 20 4e 6f 64 65 2e 6a 73>
    > new Buffer('Hello Node.js', 'base64');
    <Buffer 1d e9 65 a0 da 1d 7a 3b>
    > var buf = new Buffer(8);
    undefined
    > buf.length
    8
    > var buf = new Buffer('12345678');console.log(buf);
    <Buffer 31 32 33 34 35 36 37 38>
    undefined
    > var buf = new Buffer(7);buf.write('12345678');console.log(buf);
    <Buffer 31 32 33 34 35 36 37>
    undefined
    > var buf = new Buffer([1,2,3,4]);console.log(buf);
    <Buffer 01 02 03 04>
    undefined
    > console.log(buf[1])
    2
    undefined
    > var buf = new Buffer([1,2.33,3.11,4]);console.log(buf);
    <Buffer 01 02 03 04>
    undefined
    > console.log(buf[1])
    2
    undefined

    a.buffer.write(string, offset=0, length, encoding='utf8')方法

    > var buf = new Buffer('Hello 慕课网')
    undefined
    > buf.length
    15
    > buf.write('Hi 慕课网')
    12
    > buf.toString()
    'Hi 慕课网网'
    > buf.length
    15
    > buf.write(' ImoocImoocImooc', 2, 16)
    13
    > buf.toString()
    'Hi ImoocImoocIm'

    b.buffer.copy(target, tStart, sStart, sEnd=buffer.length)

    > var buf = new Buffer('Hello Imooc')
    undefined
    > buf.length
    11
    > var buf2 = new Buffer(5)
    undefined
    > buf.copy(buf2)
    5
    > buf2.toString()
    'Hello'
    > buf.copy(buf2, 0, 6, 11)
    5
    > buf2.toString()
    'Imooc'

    c.buffer.js源码

    var buffer = process.binding('buffer');
    process.binding它是一个桥梁,javaScript代码就可以和C++代码进行交互,这条语句获取到C++里面实现的buffer模块
    
    > var buf = new Buffer('imooc');
    undefined
    > var str = buf.toString('base64');
    undefined
    > str
    'aW1vb2M='
    > var buf = new Buffer('aW1vb2M=', 'base64')
    undefined
    > var str = buf.toString()
    undefined
    > str
    'imooc'
    > var buf = new Buffer('aW1vb2M=', 'base64')
    undefined
    > var str = buf.toString('hex')
    undefined
    > str
    '696d6f6f63'
    > var buf = new Buffer('696d6f6f63', 'hex')
    undefined
    > var str = buf.toString('utf8')
    undefined
    > str
    'imooc'
    >

    2.buffer实例buffer_image.js

    var fs = require('fs')
    
    fs.readFile('logo.png', function(err, origin_buffer) {
        console.log(Buffer.isBuffer(origin_buffer))
    
        fs.writeFile('logo_buffer.png', origin_buffer, function(err) {
            if(err) {
                console.log(err)
            }
        })
    
        //var base64Image = new Buffer(origin_buffer).toString('base64')
        var base64Image = origin_buffer.toString('base64')
    
        console.log(base64Image)
    
        var decodedImage = new Buffer(base64Image, 'base64')
    
        console.log(Buffer.compare(origin_buffer, decodedImage))
    
        fs.writeFile('logo_decoded.png', decodedImage, function(err) {
            if(err) {
                console.log(err)
            }
        })
    })
    
    //把打印出来的base64Image,然后 data:image/png;base64,base64Image串,把data:image/png;base64,base64Image串拷贝到网页图片的url()的这个括号中,就可以查看到这张图片。

    运行结果:

            在当前目录下将会看到logo_buffer.png和logo_decoded.png两个文件。

            当然,可以把输入的base64的串拷贝出来,然后在编辑器中编写如下所示:data:image/png;base64,输出的base64的串,可以把这整个串拷贝,如放到慕课网的背景url中,也能达到和慕课网本身图片一样的效果。

    3.Stream流

    stream_copy_logo.js

    var fs = require('fs')
    var source = fs.readFileSync('logo.png')
    
    fs.writeFileSync('stream_copy_logo.png', source)

    运行后,在当前目录下产生stream_copy_logo.png文件,且和logo.png一样。

    stream_event.js

    var fs = require('fs')
    
    var readStream = fs.createReadStream('stream_copy_logo.js')
    
    readStream.on('data', function(chunk) {
        console.log('data emits')
        console.log(Buffer.isBuffer(chunk))
        console.log(chunk.toString('utf8'))
    }).on('readable', function() {
        console.log('data readable')
    }).on('end', function() {
        console.log('data ends')
    }).on('close', function() {
        console.log('data close')
    }).on('error', function(e) {
        console.log('data read error' + e)
    })

    运行结果:

    stream_event02.js,流的暂停和重启

    var fs = require('fs')
    
    var readStream = fs.createReadStream('stream_copy_logo.js')
    var n = 0
    
    readStream.on('data', function(chunk) {
        n++
        console.log('data emits')
        console.log(Buffer.isBuffer(chunk))
        //console.log(chunk.toString('utf8'))
        readStream.pause()
        console.log('data pause')
        setTimeout(function() {
            console.log('data pause end')
            readStream.resume()
        }, 3000)
    }).on('readable', function() {
        console.log('data readable')
    }).on('end', function() {
        console.log(n)
        console.log('data ends')
    }).on('close', function() {
        console.log('data close')
    }).on('error', function(e) {
        console.log('data read error' + e)
    })

    运行结果:

    stream_copy_logo2.js,重构复制文件的代码

    var fs = require('fs')
    
    var readStream = fs.createReadStream('1.mp4')
    var writeStream = fs.createWriteStream('1-stream.mp4')
    
    readStream.on('data', function(chunk) {
        //数组还在缓存区
        if(writeStream.write(chunk) === false) {
            console.log('still cached')
            readStream.pause()
        }
    })
    
    readStream.on('end', function() {
        writeStream.end()
    })
    
    //对于writeStream来说,有一个drain事件,表明数据已经消费完了(写到目标了)
    writeStream.on('drain', function() {
        console.log('data drains')
        //告诉readStream可以来读了
        readStream.resume()
    })

    运行结果:

           Readable:可读流,负责读取外部的数据,并把数据缓存到内部的Buffer数组
      Writable:可写流,负责消费数据,从可读流里获取到数据,然后对得到的数据块进行处理
      Duplex:双工流,实现了Readable、Writable两个接口,如TcpSocket
      Transform:转换流,也是双工的,可读可写,实现了Readable、Writable两个接口,但不保留数据

    request_logo.js

    var http = require('http')
    var fs = require('fs')
    var request = require('request')
    
    http.createServer(function(req, res){
        // fs.readFile('logo.png', function(err, data) {
        //     if(err) {
        //         res.end('file not  exist!')
        //     }else {
        //         res.writeHeader(200, {'Context-Type': 'text/html'})
        //         res.end(data)
        //     }
        // })
    
    
        //pipe方法会自动帮我们监听data和end事件,图片文件中每一小段数据都会源源不断地发送给客户端,除此以外它还可以自动控制后端压力,这样在客户端连接缓慢时,node可以将尽可能少的缓存放到内存里面,通过对内存空间的调度就能自动控制流量,从而避免目标被快速读取的流而淹没
        //并且数据在pipe时,只有pipe链末端的目标流真正需要数据时,数据才会从源头被取出来。所以是被动消费。
        //fs.createReadStream('logo.png').pipe(res)
    
        request('https://www.imooc.com/static/img/index/logo.png').pipe(res)
    }).listen(8090)

            运行node request_logo.js后,在浏览器中输入http://localhost:8090,就能展示logo图片。

    pipe_video.js

    var fs = require('fs')
    
    fs.createReadStream('1.mp4').pipe(fs.createWriteStream('1-pipe.mp4'))

    运行后就会在当前目录下新产生1-pipe.mp4,和1.mp4一模一样。

    stream_read_write.js

    var Readable = require('stream').Readable
    var Writable = require('stream').Writable
    
    var readStream = new Readable()
    var writStream = new Writable()
    
    readStream.push('I ')
    readStream.push('Love ')
    readStream.push('Imooc
    ')
    readStream.push(null)
    
    writStream._write = function(chunk, encode, cb) {
        console.log(chunk.toString())
        cb()
    }
    
    readStream.pipe(writStream)

    运行结果:

    custom_stream.js

    var stream = require('stream')
    var util = require('util')
    
    function ReadStream() {
        stream.Readable.call(this)
    }
    
    util.inherits(ReadStream, stream.Readable)
    
    ReadStream.prototype._read = function() {
        this.push('I ')
        this.push('Love ')
        this.push('Imooc
    ')
        this.push(null)
    }
    
    function WritStream() {
        stream.Writable.call(this)
        this._cached = new Buffer('')
    }
    
    util.inherits(WritStream, stream.Writable)
    
    WritStream.prototype._write = function(chunk, encode, cb) {
        console.log(chunk.toString())
        cb()
    }
    
    function TransformStream() {
        stream.Transform.call(this)
    }
    
    util.inherits(TransformStream, stream.Transform)
    
    TransformStream.prototype._transform = function(chunk, encode, cb) {
        this.push(chunk)
        cb()
    }
    
    TransformStream.prototype._flush = function(cb) {
        this.push('Oh Yeah!')
        cb()
    }
    
    var rs = new ReadStream()
    var ws = new WritStream()
    var ts = new TransformStream()
    
    rs.pipe(ts).pipe(ws)

    运行结果:

    学习视频《进击Node.js基础(二)》。

  • 相关阅读:
    GMap.NET 随谈
    Chrome Uncaught Error: NETWORK_ERR: XMLHttpRequest Exception 101
    Java 自定义枚举
    如何得到全国省市经纬度范围
    Jquery formwizard 入门 【前台 向导功能】
    Eclipse Juno 安装 Aptana 3.3,支持jquery智能提示
    Ant学习【实践1】
    euerka总结 当幸福来敲门
    Spring Boot 热启动插件 当幸福来敲门
    ASP.NET 2.0 Provider Model 详细分析
  • 原文地址:https://www.cnblogs.com/flyingeagle/p/9032826.html
Copyright © 2020-2023  润新知