Quantcast
Channel: CNode:Node.js专业中文社区
Viewing all 14821 articles
Browse latest View live

小DEMO快速开始一个socket.io项目

$
0
0

1. 引入

1.1. koa + socket.io

const Koa = require('koa')
const app = new Koa()
const serve = require('koa-static')
const fs = require('fs')
app.use(serve('public'))  // static
// socket.io listen
const server = require('http').Server(app.callback())
const io = require('socket.io')(server)
server.listen(8080)
// socket.io logic
// ...
// ...

1.2. client

cdn:

<script src="https://cdn.bootcss.com/socket.io/2.0.4/socket.io.slim.js"></script>

connect:

let broadcast = io.connect('http://localhost:8080')

2. demo

2.1. broadcast

// server
io.on('connection', (socket) => {
    socket.broadcast.emit('user connected') // 除了当前socket之外 都会触发 user connected
})

// client
// broadcast
broadcast.on('user connected', () => {
    message.innerHTML = 'new user'
})

2.2. send-message

// server
io.on('connection', (socket) => {
    socket.on('message', function (msg) { // send(emit) message(listener)
        console.log(msg) // hi
    })
})

// client
broadcast.send('hi') // send(emit) message(listener)

2.3. emit-on-callback

let chat = io.of('/chat').on('connection', (socket) => {
    usercount++
    chat.emit('usercount', { msg: usercount }) // chat public emit
    socket.on('messagetoserver', (data, callback) => { // on
        socket.emit('messagetoclient', {
            msg: data.msg.toUpperCase(),
            username: 'server'
        }) // socket private emit
        callback('已收到') // callback
    })
})

2.4. namespace

// server
let news = io.of('/news').on('connection', (socket) => {
    // socket ...
})
let chat = io.of('/chat').on('connection', (socket) => {
    // socket ...
})

// client
let chat = io.connect('http://localhost:8080/chat')
let news = io.connect('http://localhost:8080/news')
// ...

完整代码

server:

const Koa = require('koa')
const app = new Koa()
const serve = require('koa-static')
const fs = require('fs')

app.use(serve('public'))  // static

// socket.io listen
const server = require('http').Server(app.callback())
const io = require('socket.io')(server)

server.listen(8080)

// socket.io logic

let usercount = 0

// broadcast
io.on('connection', (socket) => {
    socket.broadcast.emit('user connected') // 除了当前socket之外 都会触发 user connected
    socket.on('message', function (msg) { // send(emit) message(listener)
        console.log(msg)
    })
})

let chat = io.of('/chat').on('connection', (socket) => {
    // connect usercount
    usercount++
    chat.emit('usercount', { msg: usercount }) // public message
    socket.on('messagetoserver', (data, callback) => {
        socket.emit('messagetoclient', { msg: data.msg.toUpperCase(), username: 'server' })
        callback('已收到')
    })

    // disconnect usercount
    socket.on('disconnect', function () {
        usercount--
        chat.emit('usercount', { msg: usercount })
    })
})

let news = io.of('/news').on('connection', (socket) => {
    let newsInterval = setInterval(() => {
        socket.emit('news', { msg: 'news from ' + Date.now() })
    }, 1000)

    // disconnect
    socket.on('disconnect', () => {
        clearInterval(newsInterval)
    })
})

client:

<!DOCTYPE html>
<html="en">

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>index</title>
        <script src="https://cdn.bootcss.com/socket.io/2.0.4/socket.io.slim.js"></script>
    </head>

    <body>
        <div id="message"></div>
        <div id="thenews">news...</div>
        <div>当前在线人数
            <span id="count">0</span>
        </div>
        <input type="text" id="input">
        <ol id="msg"></ol>
        <script>
            let chat = io.connect('http://localhost:8080/chat')
            let news = io.connect('http://localhost:8080/news')
            let broadcast = io.connect('http://localhost:8080')

            let input = document.getElementById('input')
            let msg = document.getElementById('msg')
            let thenews = document.getElementById('thenews')
            let message = document.getElementById('message')

            // broadcast
            broadcast.on('user connected', () => {
                message.innerHTML = 'new user'
                setTimeout(() => {
                    message.innerHTML = ''
                }, 2000);
            })
            broadcast.send('hi') // send(emit) message(listener)

            // usercount
            chat.on('usercount', (data) => {
                document.getElementById('count').innerHTML = data.msg
            })
            // send message
            input.addEventListener('change', (e) => {
                let list = document.createElement('li')
                list.innerHTML = 'me: ' + e.target.value
                msg.appendChild(list)
                chat.emit('messagetoserver', { msg: e.target.value }, (callbackdata) => {
                    e.target.value = ''
                    list.innerHTML += ' -> ' + callbackdata
                })
            })
            // get message
            chat.on('messagetoclient', (data) => {
                let list = document.createElement('li')
                list.innerHTML = data.username + ': ' + data.msg
                msg.appendChild(list)
                list = null
            })

            // ...
            news.on('news', (data) => {
                thenews.innerHTML = data.msg
            })
        </script>
    </body>

    </html>

成都 or 远程 招募全栈 geek,在职同学可先兼职

$
0
0

目前团队陆续接到一些项目,短期内继续做外包,长远肯定是要做有期权的资金充足的外包项目,能跟着项目发展成长的项目

要求:

  1. 程序员
  2. 各种技术人才:小程序,vue,cordova、nginx、服务端、前端、后端、docker… 我只是随意列一些词在这,反正全栈就是了或者不懂得能现学现卖即可
  3. 远程,在职的同学可以先兼职,待团队稳定后再离职全职加入

团队网站:https://remote-company.com/about-us/start

基地众筹,联合创业办公模式,也许以后程序员不用担心房价啦!

$
0
0

微信群:https://remote-company.com/wechat-group基地众筹,联合创业办公模式 我和 Jeffery 都是全栈程序员(vue,react,cordova,koa2, wordpress, ios, android, web pc…甚至机器学习、区块链也学习一点),2017年下半年陆续辞职了。

快到年底的时候,Jeffery 忽然脑洞打开,邀请我一起在成都转租了个比较小的青年空间,我们的设想是能组建一支纯远程的团队,但是在各个地区有我们的基地,成员可以随时去基地聚会开 party,也可以在家办公,比如 Jeffery 现在就是在成都基地,而我则现在在福建老家深山之中!不过我们每天会微信语音或视频几分钟,持续推进共同的 idea 和各自的状态。

远期目标是希望能住在空气好,空间大,有山有水有家人的超级低消费的三四线小城镇的大别山里,然后拿着硅谷的工资水平

我们希望招募更多的相同想法的伙伴加入我们,可以暂时都不用离职,可以以会员费的形式加入我们,这样可能有以下好处:

获得一个线下交流同行,更深入认识好朋友的机会 定期周末可以一起集合到空间,搞真正的“黑客马拉松”,马拉松应该是时间维持比较长的,感觉现在外面1、2天的马拉松根本是100米短跑啊!要马拉松就应该维持几个月,每周来搞2天那种的! 遇见更多投资人、投资机构:逐步引入投资机构入驻,我们不会只是出个 ppt,我们会组织大家一起联合起来共同维护一套开发体系,这样有新的项目或者 idea 开始的时候,我们的速度就比一般团队快很多 潜在投资升值空间:每个月的会员费在不久的将来,会引入区块链,结合联盟的商业模式,想必比大多数空头 ico 更实在些! 其他还没想到的,欢迎补充! 目前暂定,会员费:500元/月,一期只招募10名,加入前需要提供您的技术简历及 github 链接

会员权益:

随时到基地办公权力(空间随意用,但是不影响他人前提下,目前空间还有其他组织不定期开会) 写代码晚了不想回家,可以随意打地铺!(stark之前住的地方在公司旁边500米,结果还经常直接躺办公室睡觉,因为他懒得动!) 优先参加基地组织的黑客马拉松权力 优先获得参与基地的外包项目(我们的项目尽可能都是资金充足而且还有股份的项目) 网址:https://remote-company.com

分享一个基于react的博客(支持docker部署)( ╯□╰ )

$
0
0

项目demo :https://www.lizc.me

项目地址:https://github.com/bs32g1038/node-blog

项目介绍

  • 采用规范化Restful API
  • 数据库使用mongodb,模型驱动采用mongoose(why mongodb?nodejs最佳搭档,据说mongo4支持文档级的事务,wow!wow!)
  • webpack4.x构建项目(webpack4还有部分插件尚未支持,但不影响基本使用)
  • scss
  • 前后台ui均采用react,react-router
  • 图片上传后自动压缩
  • docker式部署(酷!!)
  • 支持热加载,自动更新(一句npm run dev项目跑起来就是干,爽!)

项目部署建议采用docer进行部署,方便快捷,具体可参考文章https://www.lizc.me/blog/articles/5aa377e9719441001579daae

后续
  • 项目代码优化,提升用户体验( ╯□╰ )
  • 完善后台权限控制
小二上图:

untitled1.pnguntitled2.png

logstash日志错乱问题

$
0
0

我有一个Nodejs项目,项目里用log4js的 logstashUDP 将日志发到一台logstash机器,logstash输出到elasticsearch。 遇到的问题是, 项目里a.js里 logger.debug(‘aaaaaaaa’,{a:1, b:2}) 项目里b.js里 logger.debug(‘bbbbbbb’,{c:3, d:4}) 在elastic里存储到的消息居然是 {message:‘aaaaaaaaa’, a:1,b:2,c:3,d4}就是说,把2条无关的日志合并在一条日志里了。 我完全不知道是怎么回事。

log4js.json配置 image.png

logstash配置

input {
    udp {
       port => "5050"
       codec => "json"
    }
}
output {
    elasticsearch {
        hosts => "xxxxxx"
        index => "bd-%{+YYYY.MM.dd}"
    }
}

测试一下论坛

记一次爬虫遇到的坑,初探分段下载

$
0
0

遇到的问题

我去年写了一个爬虫传送门,这个爬虫主要是爬某国外网站,获取下载链接,然后根据下载链接下载对应的视频文件到本地。爬的内容有点不可描述,主要还是为了练手吧(学技术学技术。嗯嗯)。放在github一段时间后,竟然被一个朋友关注到这个爬虫,然后他悄悄地爬视频,某一天他找到我说我的爬虫问题:下载的视频很多是不完整的,比如某个文件下载45%就停止了,不管重新下载多少次都是差不多到45%就停止了。这样每次看视频的时候,只能看到前面一点,把进度条拖动到后面视频就停止播放了,这很影响用户体验啊!!!(咳咳)

排查原因

下载的时候我使用了request模块,我一开始以为是请求参数的问题,然后去github、google查了好久,尝试了在请求头中加Connection: keep-alivegzip: true都不行,折腾了两天未果,暂时扔一边。原来答应朋友要更新版本也没更新。。。

过完年回来,每天会打开github,然后看看当天的trending,偶尔看到有人给我这个爬虫star。感觉还是挺欣慰,于是乎想把这个严重影响用户体验的问题修一修,没有啦,只是想弄清楚是怎么回事(学技术学技术)。

然后开始鼓捣,先爬取某个视频的下载链接,然后把这个链接扔到chrome,打开开发者模式看网络请求,请求头大致如下:

headers.jpg

根据请求头响应头,我没看出有什么端倪,感觉会不会是请求超时了,但是我监听了error方法,没有触发。然后又是一顿google,然后怀疑服务器端是不是allowHalfOpen问题,经过打印socket实例,排除了这个可能,再次陷入僵局。折腾了一番后我有点想放弃使用request了,而是拥抱puppeteer,因为puppeteer毕竟是基于Chromium的,因为我已经确认爬到的下载链接通过浏览器是可以下载的。但是puppeteer目前还没有下载功能,所以问题又回到了原点。

中午吃完饭,番剧都看完了。又想起了这个严重影响用户体验的问题,于是爬一个下载链接,扔chrome开发者模式,不断刷新观察网络情况。刷新了几次,我貌似发现了一些猫腻,先上一张图:

debug.jpg

老铁们看出猫腻了没?可能刚看完番剧心情比较好,我发现在浏览器打开下载链接的时候,同一个下载链接请求了两次。(注:在浏览器直接打开下载链接,会直接在浏览器播放视频)直觉告诉我问题肯定出在这两次请求上,于是分析这两次请求,第一个请求如下:

request1.jpg

第二个请求如下:

request2.jpg

对比两次请求:

  • 第一次请求的Status Code200,第二次请求的Status Code206
  • 第二次请求的响应头里多了一个Content-Range属性。

又是直觉告诉我这个Content-Range有古怪,于是去查找相关资料。官方的解释是:

The Content-Range response HTTP header indicates where in a full body message a partial message belongs.

大致的意思是:Content-Range在响应头中表明本次请求的内容只是一个完整的body的一个部分。也就是说,一个完整的文件被分成了多个部分返回给客户端,所以客户端需要通过多次请求才能完整的接收整个文件。这下问题就清晰了。

解决问题

经过上面的分析,我知道了:我的爬虫程序只进行了上述的第一次请求,得到的Status Code200,而且响应头中并没有Content-Range属性。我一开始想的是,我的爬虫干脆也先后发起两次请求,在第二次请求中获取Content-Range属性进行下一步操作。后来想想这样太麻烦了,应该还有更简单的方法。于是就琢磨了这个方法:发起一次请求,拿到文件的总大小(响应头中的Content-Length),然后根据文件的大小判断要不要分段下载。经过简单的测试,当文件大小超过50M的时候,服务器返回的时候就会分段返回,因为我的爬虫默认下载最高质量的文件,一般是720P(心中无码,自然高清),所以下载的文件很多都是不完整的。

最终敲定方法:如果文件大于20M,就分段下载,否则就直接下载。分段下载是多个文件,等所有文件下载完后,再把这些文件拼接成一个完整的文件,最后把分段下载的文件删掉即可。

于是乎开始改造爬虫,核心代码如下:

const maxChunkLen = 20 * 1024 * 1024; // 20M

if (ctLength > maxChunkLen) {
          log.info('the file is big, need to split to pieces...');
          const rgs = [];
          const num = parseInt(ctLength / maxChunkLen);
          const mod = parseInt(ctLength % maxChunkLen);
          for (let i = 0; i < num; i++) {
            const rg = {
              start: i === 0 ? i : i * maxChunkLen + 1,
              end: (i + 1) * maxChunkLen
            };
            rgs.push(rg);
          }

          if (mod > 0) {
            const rg = {
              start: num * maxChunkLen + 1,
              end: ctLength
            };
            rgs.push(rg);
          }

          const pms = [];
          const files = [];
          rgs.forEach((item, idx) => {
            const copyOpts = _.cloneDeep(opts);
            copyOpts.headers['Range'] = `bytes=${item.start}-${item.end}`;
            copyOpts.headers['Connection'] = 'keep-alive';

            const file = path.join(dir, `${ditem.key}${idx}`);
            files.push(file);
            const pm = new Promise((resolve, reject) => {
              request.get(copyOpts)
                .on('error', err => {
                  reject(err);
                })
                .pipe(fse.createWriteStream(file, { encoding: 'binary' }))
                .on('close', () => {
                  resolve(`file${idx} has been downloaded!`);
                });
            });
            pms.push(pm);
          });

          log.info('now, download pieces...');
          return Promise.all(pms).then(arr => {
            // concat files
            log.info('now, concat pieces...');
            const ws = fse.createWriteStream(dst, { flag: 'a' });
            files.forEach(file => {
              const bf = fse.readFileSync(file);
              ws.write(bf);
            });
            ws.end();

            // delete temp files
            log.info('now, delete pieces...');
            files.forEach(file => {
              fse.unlinkSync(file);
            });

            return resolve(`${dst} has been downloaded!`);
          }).catch(err => {
            return reject(err);
          });
        }

经过改造后,文件都能下载完整了。看视频的时候就可随意拖动进度条了(学技术学技术,嗯嗯):

files.jpg

后记

这个问题断断续续困扰了我好几天,最后还是把这个严重影响用户体验的问题解决了。走了不少弯路,查了一些关于http的东西,也算是有点收获。最重要的是了解了分段下载这个场景,以及Content-Range的用法。需要注意以下几点:

  • 请求时,在请求头中添加Range属性来设置分段下载的文件大小范围,值的格式为:bytes=start-end
  • 文件大小范围start要从0开始,不能从1开始,否则拼接后的文件无法打开。
  • 分段下载时,每一个下载的子进程获取的文件大小范围不要有交叉也不能忽略某部分,必须是连续的。

参考资料

https://emacsist.github.io/2015/12/29/Http-%E5%8D%8F%E8%AE%AE%E4%B8%AD%E7%9A%84Range%E8%AF%B7%E6%B1%82%E5%A4%B4%E4%BE%8B%E5%AD%90/

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Range

Any 有意思 or 有意义 的 事 or 项目 then 拉我下。

$
0
0

想在,闲暇时间,跟志同之士 搞点事情。

谢谢。


AsyncJS 异步流程控制DEMO详细介绍

$
0
0

1. 基本流程

串行流程、并行流程、混合执行 series, waterfall; parallel, parallelLimit; auto;

1.1. 串行流程

1.1.1. series(多个函数依次执行,之间没有数据交换)

有多个异步函数需要依次调用,一个完成之后才能执行下一个。各函数之间没有数据的交换,仅仅需要保证其执行顺序。这时可使用series。

async.series([
    function(callback) {
        // do some stuff ...
        callback(null, 'one');
    },
    function(callback) {
        // do some more stuff ...
        callback(null, 'two');
    }
],
// optional callback
function(err, results) {
    // results is now equal to ['one', 'two']
});

另外还需要注意的是:多个series调用之间是不分先后的,因为series本身也是异步调用。

1.1.2. waterfall(多个函数依次执行,且前一个的输出为后一个的输入)

与seires相似,按顺序依次执行多个函数。不同之处,每一个函数产生的值,都将传给下一个函数。如果中途出错,后面的函数将不会被执行。错误信息以及之前产生的结果,将传给waterfall最终的callback。

注意,该函数不支持json格式的tasks

async.waterfall([
    function(callback) {
        callback(null, 'one', 'two');
    },
    function(arg1, arg2, callback) {
        // arg1 now equals 'one' and arg2 now equals 'two'
        callback(null, 'three');
    },
    function(arg1, callback) {
        // arg1 now equals 'three'
        callback(null, 'done');
    }
], function (err, result) {
    // result now equals 'done'
});

1.2. 并行流程

1.2.1. parallel(多个函数并行执行)

并行执行多个函数,每个函数都是立即执行,不需要等待其它函数先执行。传给最终callback的数组中的数据按照tasks中声明的顺序,而不是执行完成的顺序。

如果某个函数出错,则立刻将err和已经执行完的函数的结果值传给parallel最终的callback。其它未执行完的函数的值不会传到最终数据,但要占个位置。

同时支持json形式的tasks,其最终callback的结果也为json形式。

示例代码:

async.parallel([
    function(callback) {
        setTimeout(function() {
            callback(null, 'one');
        }, 200);
    },
    function(callback) {
        setTimeout(function() {
            callback(null, 'two');
        }, 100);
    }
],
// optional callback
function(err, results) {
    // the results array will equal ['one','two'] even though
    // the second function had a shorter timeout.
});

以json形式传入tasks

async.parallel({
    one: function(callback) {
        setTimeout(function() {
            callback(null, 1);
        }, 200);
    },
    two: function(callback) {
        setTimeout(function() {
            callback(null, 2);
        }, 100);
    }
}, function(err, results) {
    // results is now equals to: {one: 1, two: 2}
});

1.2.2. parallelLimit(多个函数并行执行并限制同时执行的最大数量)

parallelLimit 与上面的 parallel 最大的不同就是限制最大同时执行的数量

async.parallelLimit([(cb) => {
  setTimeout(() => {
    cb(null, 'one');
  }, 1000);
}, (cb) => {
  setTimeout(() => {
    cb(null, 'two');
  }, 2000);
}], 1, (err, value) => {
  log(value);
});
// 需时3s左右

1.3. 混合执行

1.3.1. auto(tasks, [callback]) (多个函数有依赖关系,有的并行执行,有的依次执行)

用来处理有依赖关系的多个任务的执行。比如某些任务之间彼此独立,可以并行执行;但某些任务依赖于其它某些任务,只能等那些任务完成后才能执行。

如异步获取两个数据并打印:

async.auto({
  getData: function (callback) {
    setTimeout(() => {
      log('data got')
      callback(null, 'data');
    }, 3000);
  },
  getAnotherData: function (callback) {
    setTimeout(() => {
      log('another data got')
      callback(null, 'another data');
    }, 1000);
  },
  printData: ['getData', 'getAnotherData', function (result, callback) {
    log(result);
  }]
});
// another data got
// data got
// { getAnotherData: 'another data', getData: 'data' }

1.3.2. autoInject(tasks, callbackopt) (与auto类似但回调函数被作为参数传入下一个串行函数)

Dependent tasks are specified as parameters to the function

可以说算是 auto 方法的语法糖

async.autoInject({
    getData: function (callback) {
        axios({ methods: 'get', url: 'http://baidu.com/' }).then(d => callback(null, d.status))
    },
    getAnotherData: function (callback) {
        axios({ methods: 'get', url: 'http://sogou.com/' }).then(d => callback(null, d.status))
    },
    writeFile: function (getData, getAnotherData, callback) {
        fs.writeFile('./d.json', JSON.stringify([getData, getAnotherData]), function (err) {
            if (err) { callback(err) } else { callback(null, 'finish') }
        })
    }
}, function (err, result) {
    if (err) { console.log(err) } else {
        console.log(result) // { getData: 200, getAnotherData: 200, writeFile: 'finish' }
    }
})

2. 循环流程

2.1. whilst(用可于异步调用的while)

相当于while,但其中的异步调用将在完成后才会进行下一次循环。举例如下:

var count = 0;
async.whilst(
  () => { return count < 5; }, // 是否满足条件
  (callback) => { // 满足条件执行函数
    count++;
    setTimeout(() => {
      log(count)
      callback(null, count);
    }, 1000);
  },
  (err, value) => { // 不满足条件完成循环
    log('result: ' + count);
  }
);

2.2. doWhilst (后验证的异步while)

首先执行函数

count = 0;
async.doWhilst((callback) => { // 先执行函数
  count++;
  setTimeout(() => {
    log(count);
    callback(null, count);
  }, 1000);
}, () => { // 后验证条件
  return count < 5;
}, (err, value) => { // 主回调
  log('result: ' + count);
});

2.3. until (与while相似,但判断条件相反)

var count = 0;
async.until(
  () => { return count >= 5; },
  (callback) => {
    count++;
    setTimeout(() => {
      log(count)
      callback(null, count);
    }, 1000);
  },
  (err, value) => {
    log('result: ' + count);
  }
);

2.4. doUntil (后验证Until循环)

var count = 0;
async.doUntil(
  (callback) => { // 先执行
    count++;
    setTimeout(() => {
      log(count)
      callback(null, count);
    }, 1000);
  },
  () => { return count >= 5; }, // 后验证
  (err, value) => {
    log('result: ' + count);
  }
);

2.5. during (类似whilst,回调判断)

与whilst类似,但测试的是一个异步函数的回调(err, true or false),判断回调第二参数是否为真

count = 0
async.during(callback => {
    callback(null, count < 5)
}, callback => {
    count++
    console.log(count)
    setTimeout(callback, 1000);
}, err => {
    if (err) console.log(err)
    console.log('finish')
})

2.6. doDuring (类似doWhilst,回调判断)

count = 0
async.doDuring(
    function (callback) { // 先执行一次
        count++;
        log(count);
        setTimeout(callback, 1000);
    },
    function (callback) { // 判断是否满足条件
        return callback(null, count < 5);
    },
    function (err) {
        // 5 seconds have passed
        log('finished');
    }
);

2.7. retry (按照频率重复执行多次直到成功位置)

在返回错误之前,尝试从函数获得成功响应的次数不超过规定的次数。 如果任务成功,则回调将传递成功任务的结果。 如果所有尝试都失败,回调将传递最终尝试的错误和结果。

async.retry({times: 3, interval: 200}, apiMethod, function(err, result) {
    log(result);
}); // 尝试三次,间隔200,打印结果

2.8. retryable

包装任务使其可以retry

async.auto({
    dep1: async.retryable(3, getFromFlakyService),
    process: ["dep1", async.retryable(3, function (results, cb) {
        maybeProcessData(results.dep1, cb);
    })]
}, callback);

2.9. times (重复调用 n 次)

调用 n 次,序号 n 可作为参数传入函数

async.times(5, (n, next) => {
    axios({ methods: 'get', url: 'http://sogou.com/' }).then(d => next(null, d.status)).catch(e => next(e))
}, (err, result) => {
    if (err) {
        console.log(err)
    } else {
        console.log(result) // [ 200, 200, 200, 200, 200 ]
    }
})

2.10. timesLimit (与times类似,但可限制并行执行最大数量)

timesLimit(count, limit, iteratee, callback)

2.11. timesSeries (只可并行执行一个)

The same as times but runs only a single async operation at a time.

timesSeries(n, iteratee, callback)

2.12. forever (除非捕获到错误,将允许一直执行——调用自己)

记得调用 next

async.forever((next) => { setTimeout(() => { axios({ methods: ‘get’, url: ‘http://www.baidu.com/’ }).then(d => console.log(d.status)).then(next) }, 1000) }, error => { if (error) { console.log(error) } })

3. 集合流程

Async提供了很多针对集合的函数,可以简化我们对集合进行异步操作时的步骤

3.1. each 集合中的元素执行函数

对所有集合中的所有元素,都执行同一个函数,并行:

Note, that since this function applies iteratee to each item in parallel, there is no guarantee that the iteratee functions will complete in order.不能够保证按照顺序完成

let data = [
  'http://www.baidu.com',
  'http://www.sogou.com',
  'http://www.bing.com',
];

async.each(data, (item, callback) => {
  console.log('processing...' + item);

  axios({
    methods: 'get',
    url: item
  }).then(d => {
    console.log(d.data.length);
    callback();
  });

}, (err) => {
  if (err) console.log(err);
});

// processing...http://www.baidu.com
// processing...http://www.sogou.com
// processing...http://www.bing.com
// 22430
// 111984
// 116593

3.2. eachLimit

The same as each but runs a maximum of limit async operations at a time.

let data = [
  'http://www.baidu.com',
  'http://www.sogou.com',
  'http://www.bing.com',
];

async.eachLimit(data, 2, (item, callback) => {
  console.log('processing...' + item);

  axios({
    methods: 'get',
    url: item
  }).then(d => {
    console.log(d.data.length);
    callback();
  });

}, (err) => {
  if (err) console.log(err);
});

// processing...http://www.baidu.com
// processing...http://www.sogou.com
// 22430
// processing...http://www.bing.com
// 112087
// 116593

3.3. eachOf/forEachOf

Like each, except that it passes the key (or index) as the second argument to the iteratee. 执行的函数有三个参数分别是:item, index, callback

let data = [
  'http://www.baidu.com',
  'http://www.sogou.com',
  'http://www.bing.com',
];

// async.forEachOf(data, (item, index, callback) => { forEachOf 与 eachOf 相同
async.eachOf(data, (item, index, callback) => {
  console.log('processing...NO.' + index);

  axios({
    methods: 'get',
    url: item
  }).then(d => {
    console.log(d.data.length);
    callback();
  });

}, (err) => {
  if (err) console.log(err);
});
// processing...NO.0
// processing...NO.1
// processing...NO.2
// 112477
// 22430
// 116593

3.4. eachOfLimit/forEachOfLimit

The same as eachOf but runs a maximum of limit async operations at a time. 多个限制参数,不再累述

3.5. eachOfSeries/forEachOfSeries

The same as eachOf but runs only a single async operation at a time. 相当于eachOfLimit 限制为 1

let data = [
  'http://www.baidu.com',
  'http://www.sogou.com',
  'http://www.bing.com',
];

async.eachOfSeries(data, (item, index, callback) => {
  console.log('processing...NO.' + index);

  axios({
    methods: 'get',
    url: item
  }).then(d => {
    console.log(d.data.length);
    callback();
  });

}, (err) => {
  if (err) console.log(err);
});
// processing...NO.0
// 111979
// processing...NO.1
// 22430
// processing...NO.2
// 116593

3.6. eachSeries/forEachSeries

相当于 eachLimit 限制为 1

3.7. map 返回集合数据执行函数的结果集合

there is no guarantee that the iteratee functions will complete in order 不保证按顺序完成

let data = [
  'http://www.baidu.com',
  'http://www.sogou.com',
  'http://www.bing.com',
];

async.map(data, (item, callback) => {
  console.log('processing...NO.' + item);

  axios({
    methods: 'get',
    url: item
  }).then(d => {
    callback(null, d.data.length);
  });

}, (err, value) => {
  if (err) console.log(err);
  console.log(value);
});
// processing...NO.http://www.baidu.com
// processing...NO.http://www.sogou.com
// processing...NO.http://www.bing.com
// [112677, 22430, 116593]

3.8. mapLimit

与 map 相似,多了限制参数

3.9. mapSeries

相当于 mapLimit 限制 1

3.10. mapValues

用于处理对象

A relative of map, designed for use with objects. 针对遍历对象值的情况

let obj = {
  site1: 'http://www.baidu.com',
  site2: 'http://www.sogou.com',
  site3: 'http://www.bing.com'
};

async.mapValues(obj, (value, key, callback) => {
  console.log('processing...NO.' + key);

  axios({
    methods: 'get',
    url: value
  }).then(d => {
    callback(null, d.data.length);
  });

}, (err, value) => {
  if (err) console.log(err);
  console.log(value);
});
// processing...NO.site1
// processing...NO.site2
// processing...NO.site3
// { site1: 112122, site2: 22430, site3: 116593 }

3.11. mapValuesLimit

多了限制参数

3.12. mapValuesSeries

相当于 mapValuesLimit 限制 1

3.13. filter 返回满足条件的原始元素

返回的是通过测试的原始数据,只有筛选,没有修改原始数据

Returns a new array of all the values in coll which pass an async truth test. 过滤返回经过验证结果为真的

This operation is performed in parallel, but the results array will be in the same order as the original. 最终结果将与原始数据的顺序一致

let data = [
  'http://www.baidu.com',
  'http://www.sogou.com',
  'http://www.bing.com',
];

async.filter(data, (item, callback) => {
  console.log('processing...NO.' + item);

  axios({
    methods: 'get',
    url: item
  }).then(d => {
    let length = d.data.length;
    if (length > 100000) {
      callback(null, true);
    } else {
      callback(null, false);
    }
  });

}, (err, value) => {
  if (err) console.log(err);
  console.log(value);
});
// processing...NO.http://www.baidu.com
// processing...NO.http://www.sogou.com
// processing...NO.http://www.bing.com
// ['http://www.baidu.com', 'http://www.bing.com']

3.14. filterLimit

3.15. filterSeries

3.16. reject 剔除满足条件的 (与filter相反)

与filter相反,通过验证的,予以剔除

The opposite of filter. Removes values that pass an async truth test.

3.17. rejectLimit

3.18. rejectSeries

3.19. detect 第一个满足条件的

Returns the first value in coll that passes an async truth test. 获取集合中第一个满足测试的数据

let data = [
  'http://www.sogou.com',
  'http://www.baidu.com',
  'http://www.bing.com',
];

async.detect(data, (item, callback) => {
  console.log('processing...NO.' + item);

  axios({
    methods: 'get',
    url: item
  }).then(d => {
    let length = d.data.length;
    if (length > 100000) {
      callback(null, true);
    } else {
      callback(null, false);
    }
  });

}, (err, value) => {
  if (err) console.log(err);
  console.log(value);
});
// processing...NO.http://www.sogou.com
// processing...NO.http://www.baidu.com
// processing...NO.http://www.bing.com
// http://www.baidu.com

3.20. detectLimit

3.21. detectSeries

3.22. some/any 至少有一个符合条件的触发主回调

If any iteratee call returns true, the main callback is immediately called. 一旦有符合条件的数据,马上触发主回调函数

let data = [
  'http://www.sogou.com',
  'http://www.baidu.com',
  'http://www.bing.com',
];

async.some(data, (item, callback) => {

  axios({
    methods: 'get',
    url: item
  }).then(d => {
    let length = d.data.length;
    if (length > 100000) {
      callback(null, true);
    } else {
      callback(null, false);
    }
  });

}, (err, isSatisfied) => {
  if (err) console.log(err);
  console.log(isSatisfied); // true
});

3.23. someLimit/anyLimit

3.24. someSeries/anySeries

3.25. every/all 全部满足条件返回true

Returns true if every element in coll satisfies an async test. If any iteratee call returns false, the main callback is immediately called. 全部满足条件时返回true

let data = [
  'http://www.sogou.com',
  'http://www.baidu.com',
  'http://www.bing.com',
];

async.every(data, (item, callback) => {

  axios({
    methods: 'get',
    url: item
  }).then(d => {
    let length = d.data.length;
    if (length > 0) {
      callback(null, true);
    } else {
      callback(null, false);
    }
  });

}, (err, isSatisfied) => {
  if (err) console.log(err);
  console.log(isSatisfied); // true
});

3.26. everyLimit/allLimit

3.27. everySeries/allSeries

3.28. concat 合并结果

将结果合并,同样的,不能保证结果按照原来的顺序排序

let data = [
  'http://www.sogou.com',
  'http://www.baidu.com',
  'http://www.bing.com',
];

async.concat(data, (item, callback) => {

  axios({
    methods: 'get',
    url: item
  }).then(d => {
    callback(null, { item: item, length: d.data.length });
  });

}, (err, value) => {
  if (err) console.log(err);
  console.log(value);
});
// [ { item: 'http://www.sogou.com', length: 22430 },
//   { item: 'http://www.baidu.com', length: 111979 },
//   { item: 'http://www.bing.com', length: 116550 } ]

3.29. concatLimit

3.30. concatSeries

3.31. groupBy 结果根据对象的key分组

传入一系列对象,并根据设置的 key 进行分组

let data = [{
  year: 2001,
  url: 'http://www.baidu.com'
}, {
  year: 2001,
  url: 'http://www.sogou.com'
}, {
  year: 2017,
  url: 'http://www.bing.com'
}]

async.groupBy(data, (item, callback) => {

  axios({
    methods: 'get',
    url: item.url
  }).then(d => {
    callback(null, item.year); // 按照 year 分组
  });

}, (err, value) => {
  if (err) console.log(err);
  console.log(value);
});
// {
//   '2001':
//   [{ year: 2001, url: 'http://www.baidu.com' },
//   { year: 2001, url: 'http://www.sogou.com' }],
//     '2017': [{ year: 2017, url: 'http://www.bing.com' }]
// }

3.32. groupByLimit

3.33. groupBySeries

3.34. reduce 串行累加集合数据

Reduces coll into a single value 逐渐累加

This function only operates in series. 只支持 series 模式,不支持并行

This function is for situations where each step in the reduction needs to be async; if you can get the data before reducing it, then it’s probably a good idea to do so. 适合每一 reduce 步都需要异步获取数据的情况

async.reduce([2, 3, 4], 1, (memo, item, callback) => { // 1 为 memo

  setTimeout(() => {
    callback(null, memo + item);
  }, 100);

}, (err, value) => {
  if (err) console.log(err);
  console.log(value); // 10
});

3.35. reduceRight

Same as reduce, only operates on array in reverse order. 与reduce类似

3.36. srotBy 按顺序排列

排列顺序(item / item * -1)

async.sortBy([13, 21, 321, 421, 3, 21, , , , 23121, 1], (item, callback) => {

  setTimeout(() => {
    callback(null, item * -1); // 从大到小
    // callback(null, item); // 从小到大
  }, 100);

}, (err, value) => {
  if (err) console.log(err);
  console.log(value);
});
// [23121, 421, 321, 21, 21, 13, undefined, undefined, undefined, 3, 1]

3.37. transform 通过某种规则转化集合

acc 意为 accumulate,obj 则是 object

async.transform([1, 2, 3], (acc, item, index, callback) => {

  setTimeout(() => {
    acc.push(index + ': ' + item);
    callback(null);
  }, 100);

}, (err, value) => {
  if (err) console.log(err);
  console.log(value);
});
// ['0: 1', '1: 2', '2: 3']

对象的例子:

async.transform({ name: 'oli', age: 12 }, (obj, val, key, callback) => {

  setTimeout(() => {
    obj[key] = val + '...';
    callback(null);
  }, 100);

}, (err, value) => {
  if (err) console.log(err);
  console.log(value);
});
// { name: 'oli...', age: '12...' }

4. 其他

4.1. tryEach ()

If one of the tasks were successful, the callback will be passed the result of the successful task 一旦其中一个成功,则callback返回该成功的任务的返回值

It runs each task in series but stops whenever any of the functions were successful. 测试哪个成功

async.tryEach([
    function getDataFromFirstWebsite(callback) {
        // Try getting the data from the first website
        callback(err, data);
    },
    function getDataFromSecondWebsite(callback) {
        // First website failed,
        // Try getting the data from the backup website
        callback(err, data);
    }
],
// optional callback
function(err, results) {
    Now do something with the data.
});

4.2. race (哪个优先结束)

并行运行任务函数的数组,一旦任何一个完成或传递错误信息,主回调将立即调用。相当于 Promise.race()

async.race([
    function(callback) {
        setTimeout(function() {
            callback(null, 'one');
        }, 200);
    },
    function(callback) {
        setTimeout(function() {
            callback(null, 'two'); // 优先触发
        }, 100);
    }
],
// 主回调
function(err, result) {
    // the result will be equal to 'two' as it finishes earlier
});

4.3. compose (将多个异步函数串联在一起返回一个新的函数 类似 f(g(h())))

把f(),g(),h()异步函数,组合成f(g(h()))的形式

Each function consumes the return value of the function that follows

注意执行的顺序,这里的 add1mul3 即为 先执行 mul3 然后执行 add1

function add1(n, callback) {
  setTimeout(function () {
    callback(null, n + 1);
  }, 10);
}

function mul3(n, callback) {
  setTimeout(function () {
    callback(null, n * 3);
  }, 10);
}

var add1mul3 = async.compose(mul3, add1); // 1) add1() 2) mul3()
add1mul3(4, function (err, result) {
  console.log(result); // 15
});

4.4. seq

Each function consumes the return value of the previous function. 与 compose 类似

pp.get('/cats', function(request, response) {
    var User = request.models.User;
    async.seq(
        _.bind(User.get, User),  // 'User.get' has signature (id, callback(err, data))
        function(user, fn) {
            user.getCats(fn);      // 'getCats' has signature (callback(err, data))
        }
    )(req.session.user_id, function (err, cats) {
        if (err) {
            console.error(err);
            response.json({ status: 'error', message: err.message });
        } else {
            response.json({ status: 'ok', message: 'Cats found', data: cats });
        }
    });
});

4.5. apply (给函数预绑定参数)

async.parallel([
    async.apply(fs.writeFile, 'testfile1', 'test1'),
    async.apply(fs.writeFile, 'testfile2', 'test2')
]);

4.6. applyEach

async.applyEach([enableSearch, updateSchema], 'bucket', callback);

4.7. applyEachSeries

only a single async operation at a time.

4.8. queue (串行的消息队列)

是一个串行的消息队列,通过限制了worker数量,不再一次性全部执行。当worker数量不够用时,新加入的任务将会排队等候,直到有新的worker可用

If all workers are in progress, the task is queued until one becomes available

queue(worker, concurrency) concurrency 用来定义worker的数量

let q = async.queue(function (task, callback) {
  console.log('deal with ' + task.url);
  setTimeout(() => {
    axios({
      methods: 'get',
      url: task.url
    }).then((d) => {
      console.log(d.data.length);
      callback();
    });
  }, 2000);
}, 1);

q.drain = function () {
  console.log('all done');
}
q.push({ url: 'http://www.baidu.com' }, function (err) {
  if (err) {
    console.log(err);
  }
});
q.unshift([{ url: 'http://www.baidu.com' }, { url: 'http://www.sogou.com' }], function (err) {
  if (err) {
    console.log(err);
  }
});
// deal with http://www.sogou.com
// 22430
// deal with http://www.baidu.com
// 112041
// deal with http://www.baidu.com
// 112220
// all done

4.9. priorityQueue(worker, concurrency)

与 queue 不同的是,push 可以设置 priority push(task, priority, [callback])

4.10. cargo (串行的消息队列,每次负责一组任务)

While queue passes only one task to one of a group of workers at a time, cargo passes an array of tasks to a single worker, repeating when the worker is finished. queue 一个 worker 负责一个任务,但 cargo 则是一个 worker 负责一组任务。

// create a cargo object with payload 2
var cargo = async.cargo(function(tasks, callback) {
    for (var i=0; i<tasks.length; i++) {
        console.log('hello ' + tasks[i].name);
    }
    callback();
}, 2);

// add some items
cargo.push({name: 'foo'}, function(err) {
    console.log('finished processing foo');
});
cargo.push({name: 'bar'}, function(err) {
    console.log('finished processing bar');
});
cargo.push({name: 'baz'}, function(err) {
    console.log('finished processing baz');
});

4.11. nextTick ()

浏览器使用 setImmediate

Calls callback on a later loop around the event loop

var call_order = [];

async.nextTick(function () {
  call_order.push('two'); // 4
  console.log(call_order);
});

setTimeout(() => {
  call_order.push('four'); // 7
  console.log(call_order);
}, 100);

async.nextTick(function () {
  call_order.push('three'); // 5
  console.log(call_order);
});

console.log(call_order); // 1

call_order.push('one'); // 2

console.log(call_order); // 3

async.setImmediate(function (a, b, c) {
  console.log(a, b, c); // 6
}, 1, 2, 3);

4.12. asyncify (同步函数转化成异步函数)

直接传入同步方法:

async.waterfall([
    async.apply(fs.readFile, './d.json', "utf8"),
    async.asyncify(JSON.parse), // 同步函数
    function (data, next) { // 转成异步
        console.log(data) // data...
        next(data) // passing data...
    }
], (data) => {
    console.log(data); // data...
});

包裹一层函数:

async.waterfall([
    async.asyncify(function () {
        // 同步函数转称异步函数传入参数需要用到一层function并return结果
        return fs.readFileSync('./d.json', "utf8")
    }),
    function (data, next) { // 执行回调并调用 next
        console.log(data.length)
        next(data)
    },
    async.asyncify(JSON.parse),
    function (data, next) {
        console.log(data)
        next(data)
    }
], (data) => {
    console.log(data);
});

4.13. constant (一般用来给串行流程的下个阶段传值)

如asyncify的例子可以修改为:

async.waterfall([
    async.constant('./d.json', 'utf8'), // 设置参数
    fs.readFile, // 调用参数
    function (data, next) {
        console.log(data.length)
        next(data)
    },
    async.asyncify(JSON.parse),
    function (data, next) {
        console.log(data)
        next(data)
    }
], (data) => {
    console.log(data);
});

4.14. dir (调用console.dir)

Logs the result of an async function to the console using console.dir to display the properties of the resulting object. 注意浏览器兼容性 console.dir 适用于 FireFox and Chrome

// in a module
var hello = function(name, callback) {
    setTimeout(function() {
        callback(null, {hello: name});
    }, 1000);
};

// in the node repl
node> async.dir(hello, 'world');
{hello: 'world'}

4.15. ensureAsync (确保任务异步执行)

function sometimesAsync(arg, callback) {
    if (cache[arg]) {
        return callback(null, cache[arg]); // this would be synchronous!!
    } else {
        doSomeIO(arg, callback); // this IO would be asynchronous
    }
}

// this has a risk of stack overflows if many results are cached in a row
async.mapSeries(args, sometimesAsync, done);

// this will defer sometimesAsync's callback if necessary,
// preventing stack overflows
async.mapSeries(args, async.ensureAsync(sometimesAsync), done);

4.16. log (调用console.log)

// in a module
var hello = function(name, callback) {
    setTimeout(function() {
        callback(null, 'hello ' + name);
    }, 1000);
};

// in the node repl
node> async.log(hello, 'world');
'hello world'

4.17. memoize (缓存结果)

Caches the results of an async function. When creating a hash to store function results against, the callback is omitted from the hash and an optional hash function can be used.

var slow_fn = function(name, callback) {
    // do something
    callback(null, result);
};
var fn = async.memoize(slow_fn);

// fn can now be used as if it were slow_fn
fn('some name', function() {
    // callback
});

例如:

var slow_fn = function (url, callback) {
    axios({ methods: 'get', url }).then(e => {
        console.log(e.status)
        callback(null, e.data)
    })
};
var fn = async.memoize(slow_fn);

fn('http://baidu.com', function (e, data) {
    console.dir('done')
});
fn('http://sogou.com', function (e, data) {
    console.dir('done')
});
// 200
// 'done'
// 200
// 'done'

4.18. unmemoize

Undoes a memoized function, reverting it to the original

4.19. nextTick

var call_order = [];
async.nextTick(function() {
    call_order.push('two');
    // call_order now equals ['one','two']
});
call_order.push('one');

async.setImmediate(function (a, b, c) {
    // a, b, and c equal 1, 2, and 3
}, 1, 2, 3);

4.20. reflect (遇到error继续执行)

always completes with a result object, even when it errors.

async.parallel([
    async.reflect(function(callback) {
        // do some stuff ...
        callback(null, 'one');
    }),
    async.reflect(function(callback) {
        // do some more stuff but error ...
        callback('bad stuff happened');
    }),
    async.reflect(function(callback) {
        // do some more stuff ...
        callback(null, 'two');
    })
],
// optional callback
function(err, results) {
    // values
    // results[0].value = 'one'
    // results[1].error = 'bad stuff happened'
    // results[2].value = 'two'
});

例如:

async.parallel([
    async.reflect((callback) => {
        setTimeout(() => {
            callback('error')
        }, 1000)
    }),
    (callback) => {
        setTimeout(() => {
            callback(null, 'data')
        }, 2000)
    }
], (err, result) => {
    if (err) {
        console.error(err)
    } else {
        console.dir(result) // [ { error: 'error' }, 'data' ]
    }
})

4.21. reflectAll (包裹reflect)

A helper function that wraps an array or an object of functions with reflect.

let tasks = [
    function(callback) {
        setTimeout(function() {
            callback(null, 'one');
        }, 200);
    },
    function(callback) {
        // do some more stuff but error ...
        callback(new Error('bad stuff happened'));
    },
    function(callback) {
        setTimeout(function() {
            callback(null, 'two');
        }, 100);
    }
];

async.parallel(async.reflectAll(tasks), // reflectAll 包含了tasks任务列表
// optional callback
function(err, results) {
    // values
    // results[0].value = 'one'
    // results[1].error = Error('bad stuff happened')
    // results[2].value = 'two'
});

4.22. setImmediate (浏览器中相当于 setTimeout(callback, 0))

var call_order = [];
async.nextTick(function() {
    call_order.push('two');
    // call_order now equals ['one','two']
});
call_order.push('one');

async.setImmediate(function (a, b, c) {
    // a, b, and c equal 1, 2, and 3
}, 1, 2, 3);

4.23. timeout (限制异步函数timeout最长延迟,时间过了返回错误)

Sets a time limit on an asynchronous function.

If the function does not call its callback within the specified milliseconds, it will be called with a timeout error. 超过时间返回错误:‘ETIMEDOUT’

function getData(url, callback) {
    axios({ methods: 'get', url }).then(d => {
        setTimeout(() => {
            return callback(null, d.status)
        }, 1000);
    })
}

let wrappeed = async.timeout(getData, 1000)

wrappeed('http://baidu.com', (err, data) => {
    if (err) {
        console.log(err)
    } else {
        console.log(data)
    }
})
// { Error: Callback function "getData" timed out.
//     at Timeout.timeoutCallback [as _onTimeout] (/Users/apple/test/node_modules/_async@2.6.0@async/dist/async.js:4916:26)
//     at ontimeout (timers.js:386:14)
//     at tryOnTimeout (timers.js:250:5)
//     at Timer.listOnTimeout (timers.js:214:5) code: 'ETIMEDOUT' }

multer使用array方式上传,上传超过三张图片就会丢失,比如选择5张图片上传,最后只上传了2-3张,无法全部上传

$
0
0

各位大佬,在使用multer的array方式进行批量图片上传时,只要超过三张图片,上传的图片就会有丢失,比如选择5张图片上传,最后只上传了2-3张,随机的,无法全部上传到指定的文件夹下,也没有报错信息,请问有人遇到这种情况?是什么问题?,代码如下: upload.js: let storage = multer.diskStorage({ destination: (req, file , cb) => { if(typeof file != ‘undefined’){ let path = ‘./static/upload/avator’ if(req.url == ‘/api/article/release’){ path = ‘./static/upload/article’ } cb(null, path) } }, filename: function (req, file, cb) { let fileFormat = (file.originalname).split(’.’) cb(null, file.fieldname + ‘-’ + Date.now() + ‘.’ + fileFormat[fileFormat.length - 1]) } }) let upload = multer({ storage: storage }) module.exports = upload

const upload = require(’…/utils/upload’) const A = require(’…/controller/article.js’) router.post(’/article/release’, checkToken, upload.array(‘file’, 20), A.release)

实在找不到问题所在,请各位大佬指点迷津,多谢。。。

典型 Node.js 服务端内存泄漏案例01——事件侦听器泄漏

$
0
0

概要

在帮助客户排查问题的过程中,我们发现很多客户对于 Node.js 中的事件侦听器的使用存在一定的误区,所以事件侦听器的泄漏是编写 Node.js 代码的一大定时炸弹,下面我们通过一个真实的客户案例来详细解读下此类泄漏,以帮助大家避免类似的问题。

发现问题

接入 Node.js 性能平台后,我们在全局告警中看到某个客户的应用频繁提醒堆内使用内存占据堆上限超过 80%,这种情况基本上大概率就是发生内存泄漏了,联系到对应的客户后,进过客户的授权,我们看到了有问题的进程内存状况,如下图所示:

5.png

虽然图中依旧显示健康态,但是依旧可以看到趋势是堆内内存稳步上升,一些问题比较严重的业务进程直接达到堆内限制上限从而 OOM 掉。

定位问题

堆快照分析

排查内存泄漏,首先需要的就是堆快照,因为此次挑选的进程堆内内存大小约 225M,因此能顺利通过 Node.js 性能平台打印堆快照获得 HeapSnapshot,并且这份快照也能反映出内存中的一些问题。经过性能平台提供的在线分析,可以获取如下信息。

第一个信息是当前的堆结构概览:

6

第二个信息是内存泄漏报表:

7.png

展开引力图,看到疑似的泄露点引用关系如下图所示:

9.png

进一步根据引力图详细信息,可以看到内存堆积的引用文字关系如下所示(顺序):

(context) of function /home/xxxx/app/controller/home.js() / home.js @345463 -> Client @46073 的 _events 属性 -> EventHandlers @46075 的 error 属性 -> Array @46089

看到这里,熟悉 Node.js 的 Event 类实现的小伙伴就能直接判断出是 socket 创建时的 error 事件侦听器策略不当引发的内存泄漏,更简单的说,就是在同一个 socket 创建中不断侦听 error 事件导致的内存泄漏。

第三个信息是对象簇视图:

8

可以看到,确实和上面猜测的一样,app/controller/home.js 中的某个 socket 对象的 error 事件侦听器回调函数在不停增加。

代码分析

到这里可以去代码中定位具体有问题的代码了,因此又经过与此应用负责人沟通后,拿到了项目代码仓库的查看权限,查看 app/controller/home.js 文件,搜索 error ,直接找到了出问题的地方,以下是问题最小化代码:

module.exports = app => {
  class HomeController extends app.Controller {
    * demo() {
      	if (ENV === DEVELOPMENT) {
       	//开发环境下操作...         
	   } else {
          if (!client) {
              client = Client.create({
                  refreshInterval: 30000,
                  requestTimeout: 5000,
                  urllib: urllib
              })
          }
          client.on('error', err => {
          	//error 处理...
          })
          
          //其余逻辑处理...
      }
    }
  }
  return HomeController;
};

并且在 router.js 中定义的对应这个 controller 的路由如下:

app.get(/.*/, 'home.demo');

好了,可以看到,由于 client 是全局变量,此时用户每访问一次网站首页,都会给 client._events.error 对应的数组增加一个 error 处理函数,虽然每个 error 处理函数 26KB 左右,但是流量上来后,很容易累积触发 OOM 。

解决问题

理解内存泄漏产生的原因后,要解决这个问题就比较简单了,一种通用的解决办法是在 error 侦听操作放入 client 的初始化里面:

module.exports = app => {
  class HomeController extends app.Controller {
    * demo() {
      	if (ENV === DEVELOPMENT) {
       	//开发环境下操作...         
	   } else {
          if (!client) {
              client = Client.create({
                  refreshInterval: 30000,
                  requestTimeout: 5000,
                  urllib: urllib
              })
              
              client.on('error', err => {
          		//error 处理...
	          })
          }
          
          //其余逻辑处理...
      }
    }
  }
  return HomeController;
};

这样保证全局只有有一个 error 事件侦听器,性能也比较好。还有一种处理方式是每次 controller 处理完成后移除侦听器:

module.exports = app => {
  class HomeController extends app.Controller {
    * demo() {
      	if (ENV === DEVELOPMENT) {
       	//开发环境下操作...         
	   } else {
          if (!client) {
              client = Client.create({
                  refreshInterval: 30000,
                  requestTimeout: 5000,
                  urllib: urllib
              })
          }
          //定义 error 处理句柄
          const errorHandle = err => {
          		//error 处理...
	       }
          client.on('error', errorHandle);
          
          //其余逻辑处理...
          
          //移除 error 侦听器
          client.removeListener('error', errorHandle);
      }
    }
  }
  return HomeController;
};

但是这样子比第一种耗费一些额外的性能,只是作为解决事件侦听器内存泄漏的方式写出来供大家参考。

最后一种是 egg 框架推荐的写法,也是本问题的最佳解决办法,像这种进程生命周期只需要一次连接的可以放到 app/extend/application.js 中去由框架保证全局单例:

// app/extend/application.js
const CLIENT = Symbol('Application#xxClient');
module.exports = {
  get xxClient() {
    if (!this[CLIENT]) {
      this[CLIENT] = Client.create({});
      // this[CLIENT].on('error', fn);
    }
    return this[CLIENT];
  }
}

// app/controller/home.js
module.exports = app => {
  class HomeController extends app.Controller {
    * demo() {
      this.app.xxClient.xx();
    }    
  }
  return HomeController;
};

多实例的情况下 定时任务怎么设计?

$
0
0

在多实例的情况下,因为每个实例都是完全复制过来的,每个实例都有自己的定时任务,但是启用时间是一样的 用的MongoDB数据库,怎么做到区分,多实例同时跑一个任务来操作同一行数据?

Nest.js 4.6.6 发布,更优雅的下一代 Node.js 开发框架

$
0
0

Nest 是构建高效,可扩展的 Node.js Web 应用程序的框架。 它使用现代的 JavaScript 或 TypeScript(保留与纯 JavaScript 的兼容性),并结合 OOP(面向对象编程),FP(函数式编程)和FRP(函数响应式编程)的元素。在底层,Nest 使用了 Express,可以方便地使用各种可用的第三方插件。

Nest 真正使得 Node.js 也可以像 Spring 一样优雅地应用在大型项目中。

4.6.6 更新内容:

Bug修复:

common:File(s)Interceptor引发http状态码500 #465 核心:e2e测试,EXPRESS_REF注射器问题#484 微服务:NO_PATTERN_MESSAGEbugfix #491

改进:

常见:class-transformer更新#483 常见:HttpService改进,删除重复的axios接口#470 核心:当不可用的组件被导出时(模块不一致)#479,更有意义的异常

使用

github: https://github.com/nestjs/nest

中文文档: https://docs.nestjs.cn

项目推荐

基于 nest 的模块化敏捷开发系统架构:https://github.com/notadd/notadd/tree/next

Nodejs 响应http请求时,服务器无法通过该请求返回客户端数据?????

$
0
0

如题,使用的是express框架,http请求到达服务器时,缺少connect和socket导致服务器无法响应值给客户端,什么样的原因会造成这样的结果?在线等,很急!

stream.Writable()探究

$
0
0

stream.Writable是node中非常重要的类,其中的write()更是重中之重,理解这个方法的工作流程使得我们碰到node中各种Writable对象的时候心里更有底

分析之前先上write()的流程图,这个图只保留了主干部分而省略了一些细节,我们从中要留意几个重要步骤的时机和条件,它们都用黄色部分标出

  • 真正执行写操作
  • 写请求(即用户调用write()或者end()发起一个写操作的请求)的缓存与执行
  • 发射drain事件
  • 运行用户的callback

stream_write.jpg

writable()

writable()定义在文件lib/_stream_writable.js中,对部分源码做出注释

Writable.prototype.write = function(chunk, encoding, cb) {
  var state = this._writableState;//this._writableState是一个记录流内部状态的重要对象,我们稍后再分析它
  var ret = false;
  var isBuf = !state.objectMode && Stream._isUint8Array(chunk);//判断用户提供的数据类型是否为buffer
 
  if (isBuf && Object.getPrototypeOf(chunk) !== Buffer.prototype) {//转为node原生实现的buffer
    chunk = Stream._uint8ArrayToBuffer(chunk);
  }

  if (typeof encoding === 'function') {//判断是否指定了编码类型
    cb = encoding;
    encoding = null;
  }

  if (isBuf)
    encoding = 'buffer';//如果用户写入的数据类型为buffer,则编码类型必须为"buffer"
  else if (!encoding)
    encoding = state.defaultEncoding;//如果没有编码类型,则设置为默认编码

  if (typeof cb !== 'function')//如果没有提供回调函数则使用默认的回调函数
    cb = nop;

  if (state.ending)
    writeAfterEnd(this, cb);//writeAfterEnd的逻辑非常简单,先发射错误事件然后异步执行回调
  else if (isBuf || validChunk(this, state, chunk, cb)) {
    state.pendingcb++;//等待执行的callback数量加1
    ret = writeOrBuffer(this, state, isBuf, chunk, encoding, cb);//调用writeOrBuffer,由它决定是写入底层资源还是内部缓冲区
  }

  return ret;
};

write()首先对编码和用户传递过来的数据进行一些基本处理,然后观察自身状态。如果state.ending为真则抛出错误,否则进行下一步处理。从后面关于writeState的讨论我们可以知道state.ending为真的话意味着end()被调用但是尚未返回。正如文档上所说:

在调用了 stream.end()方法之后,再调用 stream.write()方法将会导致错误。

需要注意的是,写操作贯穿一系列的函数而不仅仅是当前函数,而其它函数也是由可能抛出错误的。因此文档上说明:

writable.write()方法向流中写入数据,并在数据处理完成后调用 callback。如果有错误发生, callback不一定以这个错误作为第一个参数并被调用。要确保可靠地检测到写入错误,应该监听 'error'事件。

writeOrBuffer()

接下来看writeOrBuffer(),它的源码如下:

function writeOrBuffer(stream, state, isBuf, chunk, encoding, cb) {
  if (!isBuf) {//如果不是buffer类型,就根据编码解码
    var newChunk = decodeChunk(state, chunk, encoding);
    if (chunk !== newChunk) {
      isBuf = true;
      encoding = 'buffer';
      chunk = newChunk;
    }
  }
  var len = state.objectMode ? 1 : chunk.length;//处于对象模式的话则需要写入的数据长度为1,否则为chunk的长度

  state.length += len;//这个其实就是内部缓冲区的长度
  /**
  如果内部缓冲区的长度已经超过highWaterMark,则该次写操作会返回false,
  此时外部程序应该停止调用write()直到drain事件发生为止
  **/
  var ret = state.length < state.highWaterMark;
  if (!ret)
    state.needDrain = true;//说明缓冲区已经满,需要排空它

  if (state.writing || state.corked) {//如果处于写状态中或者调用了cork(),则把当前的写入请求先缓存起来,等到适当的时候再继续调用
    var last = state.lastBufferedRequest;
    state.lastBufferedRequest = {
      chunk,
      encoding,
      isBuf,
      callback: cb,
      next: null
    };
    if (last) {//如果链表不为空
      last.next = state.lastBufferedRequest;
    } else {//如果链表为空,则将这个写请求设为链表头部
      state.bufferedRequest = state.lastBufferedRequest;
    }
    state.bufferedRequestCount += 1;//更新链表的长度
  } else {//否则开始写入
    doWrite(stream, state, false, len, chunk, encoding, cb);
  }
  return ret;
}

writeOrBuffer()如其所名,根据当前的状态缓存写操作的请求或者直接进行写操作,如果有写操作正在执行或者调用了cork(),则把当前写请求缓存起来以供后用,否则调用doWrite()进行真正的写入。缓存起来的写操作在两种情况下可以得到执行:

  • 调用uncork()的时候
  • 调用onWrite()的时候onWrite()作为参数传递给了需要用户实现的_write()_writev(),因此当实现Writable流重写这两个方法的时候,务必记得调用onWrite()。我看了一下fs.write(),确实在写入完成之后有调用它们。

至于uncork()onWrite()的调用时机我们下面再分析。

doWrite()

function doWrite(stream, state, writev, len, chunk, encoding, cb) {
  state.writelen = len;//需要写入的数据长度
  state.writecb = cb;//用户提供的回调
  state.writing = true;//标记正在进行写入
  state.sync = true;//是否同步调用
  if (writev)//_writev是批量写入数据,如果有就优先调用之
    stream._writev(chunk, state.onwrite);
  else//否则调用默认的版本
    stream._write(chunk, encoding, state.onwrite);
  state.sync = false;
}

doWrite()调用stream._writevstream._write真正执行写操作,这两个函数都是实现者负责提供的。doWrite()中设置了state.writing = true,这意味着后续的请求都会缓存起来。同时还设置了state.sync=true,意味着同步调用,这个设置项很有意思,在源码中的其它地方我们看到这样的注释:

defer the callback if we are being called synchronously to avoid piling up things on the stack

说明如果在同步模式下,用户的回调是需要异步执行,这是为了防止在栈上保存过多的信息。而异步情况下则没有这个限制,关于这样缘由不是十分清楚。

关于_writableState

_writableState是一个记录流内部状态的对象,在各个地方都会用到,在研究onWrite()等函数前需要先理解这个对象各个属性代表的意思

function WritableState(options, stream) {
  options = options || {};
  /*
  Duplex streams可以进行读和写,但是它内部共享一个option。
  在一些情况下要求读和写都需要设置option.XXXX属性的值。此时可以使用
  option.readableXXX 和 option.writableXXX来实现
  */
  var isDuplex = stream instanceof Stream.Duplex;

  this.objectMode = !!options.objectMode;

  if (isDuplex)
    this.objectMode = this.objectMode || !!options.writableObjectMode;

  // the point at which write() starts returning false
  // Note: 0 is a valid value, means that we always return false if
  // the entire buffer is not flushed immediately on write()
  this.highWaterMark = getHighWaterMark(this, options, 'writableHighWaterMark', isDuplex);  
  this.finalCalled = false;//_final()是否已经被调用  
  this.needDrain = false;// 是否需要发射drain事件 
  this.ending = false; // end()正在被调用 
  this.ended = false; // end()调用完毕返回 
  this.finished = false; //是否已经发射finish事件  
  this.destroyed = false;// 流是否已经被销毁
  /*
  字符串在传递给_write()时候是否先解码为buffer,一些node内部的流能通过这个属性在程序的底层进行优化
  */
  var noDecode = options.decodeStrings === false;
  this.decodeStrings = !noDecode;
  this.defaultEncoding = options.defaultEncoding || 'utf8';//默认编码
  /**
  缓冲区的长度,
  流的内部其实并没有维护一个真正的缓冲区,只是用了一个变量记录等待写入底层文件或者socket的数据的数量
  **/
  this.length = 0; 
  this.writing = false;//是否正在进行写操作
  this.corked = 0;//如果这个值为true,则所有的写操作请求都会被缓存直至调用了uncork()
  /*是否异步调用用户提供的callback,这个值为true的时候意味着需要异步调用用户的callback
  */
  this.sync = true;  
  this.bufferProcessing = false;//是否正在处理之前被缓冲下来的写请求  
  this.onwrite = onwrite.bind(undefined, stream);// 内部的回调,传递给 _write(chunk,cb)的回调函数
  
  this.writecb = null;// 用户的回调,传递给write(chunk,encoding,cb)  
  this.writelen = 0;// 调用_write()时候需要写入的数据的长度
  this.bufferedRequest = null;//被缓存的写请求的链表的表头
  this.lastBufferedRequest = null;//上一个被缓存的写请求
  this.pendingcb = 0;//等待被执行的用户回调函数数量,在发射finish事件的时候必须为0
  this.prefinished = false;//与同步的Transform streams相关,如果等待被执行的函数只剩下传递给_write的回调,则发射之  
  this.errorEmitted = false;//如果error事件已经发射,不应该再抛出错误  
  this.bufferedRequestCount = 0;// 链表长度
  //初始化第一个CorkedRequest,总是有一个CorkedRequest可以使用,并且内部最多维护两个CorkedRequest
  var corkReq = { next: null, entry: null, finish: undefined };
  corkReq.finish = onCorkedFinish.bind(undefined, corkReq, this);
  this.corkedRequestsFree = corkReq;
}

从上面可以看到,文档中所谓的writable流内部有一个缓冲区其实是并不存在的,流内部只是维护了一个变量,用来记录即将写入的数据的数量,如果这个数量小于writableHighWaterMark,则缓存这个写请求。因此文档中说了即使超出缓冲区大小也依然可以写是可以理解的。了解了这些状态的意义之后我们可以来看传递给_write()state.onwrite是如何利用_writableState的状态进行操作

onwrite()

onwrite()的主要功能是真实的写操作完成之后用来收尾,包括执行缓冲区中的写请求和更新流内部的状态。例如在fs.write()中可以看到onwrite()就是作为回调在执行的

WriteStream.prototype._write = function(data, encoding, cb) {
  //省略...
  fs.write(this.fd, data, 0, data.length, this.pos, (er, bytes) => {
    if (er) {
      if (this.autoClose) {
        this.destroy();
      }
      return cb(er);
    }
    this.bytesWritten += bytes;
    cb();
  });

  if (this.pos !== undefined)
    this.pos += data.length;
};
function onwrite(stream, er) {
  var state = stream._writableState;
  var sync = state.sync;
  var cb = state.writecb;//用户的callback,见上面_writeState的解释

  onwriteStateUpdate(state);//更新流内部的状态,更新之后意味着当前的写操作已经完成

  if (er)//如果_write操作发生任何错误则抛出异常,注意这个异常可能不是最原始的异常,见上面所描述
    onwriteError(stream, state, sync, er, cb);
  else {
    // 检查是否可以结束这个写操作了
    var finished = needFinish(state);

    if (!finished &&//不能处于finished状态
        !state.corked &&//不能处于corked状态
        !state.bufferProcessing &&//没有缓存的写操作正在被执行
        state.bufferedRequest) {//缓冲区不能为空
      clearBuffer(stream, state);
    }

    if (sync) {//如果是同步调用,则需要异步调用用户的callback,以防止往栈上堆积东西
      process.nextTick(afterWrite, stream, state, finished, cb);
    } else {
      afterWrite(stream, state, finished, cb);
    }
  }
}

其中onwriteStateUpdate()对流的各种状态进行了更新,包括以下几项:

function onwriteStateUpdate(state) {
  state.writing = false;//意味当前的写操作已经结束
  state.writecb = null;//清空用户提供的callback
  state.length -= state.writelen;//修改缓冲区长度
  state.writelen = 0;
}

可以看出onwriteStateUpdate()后意味着当前这个写操作已经完成。而needFinish(state)的判断标准如下:

function needFinish(state) {
  return (state.ending &&//调用了end
          state.length === 0 &&//缓冲区为空
          state.bufferedRequest === null &&//没有缓存的写操作
          !state.finished &&//finished的状态还不是true
          !state.writing);//不是正处于写操作中
}

它意味着当前写操作已经完成,并且缓冲区中的写操作也已经全部完成

总结起来就是onwrite()执行了真正的写入操作后,再去执行缓冲区中积压着的其它写请求。综上所述可以推论

  • 如果一次写操作没有完成,则剩下的写操作都会被缓存起来
  • 一旦有一项写操作完成,则会取出缓冲区中剩余的写操作并执行它们。

afterWrite()

afterWrite()是主干流程上最后一个重要的步骤了,它负责发射drain事件,通知调用者可以继续写入并且调用用户提供的callback

function afterWrite(stream, state, finished, cb) {
  if (!finished)
    onwriteDrain(stream, state);//如果不是处于finished状态,就发射drain事件
  state.pendingcb--;
  cb();//执行用户的callback
  finishMaybe(stream, state);//其它处理细节
}

function onwriteDrain(stream, state) {
  if (state.length === 0 && state.needDrain) {
    //如果缓冲区曾经超过警戒线,但是现在已经为空,就可以发射drain事件
    state.needDrain = false;
    stream.emit('drain');
  }
}

如何通过https访问部署在服务器上的api?

$
0
0

这两天我把nginx设置了https 之前的api访问地址是http://ip:3000/api/… 现在使用http://ip:3000/api/article/news仍然可以访问,但是使用https无法访问 我尝试在nginx配置文件中添加 server { listen 3000 ssl; server_name localhost:3000;

ssl_certificate cert/my.pem; ssl_certificate_key cert/my.key;

ssl_session_cache shared:SSL:1m; ssl_session_timeout 5m; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; #ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; }

重启nginx仍然无法访问,请教大家!

Mongoose连不上数据库的情况下,如果保持Experss运行

$
0
0

想请问一个问题,Mongoose连不上数据库的情况下,如果保持Experss运行。情景是因为要打包成docker容器,没有数据库的情况下也要保持单独能够,运行

区块链项目开发(年薪百万) nodejs or go?

$
0
0

区块链目前人人皆知,小到广场舞的大妈,大到企业大佬。关于开发语言的选择。从招聘网站上来看go支持率比较大。大家有什么看法?

react+egg 做一个协作聊天室

$
0
0

目标是把图片协作和表格协作都做一下,增值一下自己,千里之行始于足下。 目前还比较简单,只实现了简单的聊天房间和图片处理,慢慢完善

前端部分 :react+mobx+react-routerv4+socket.io+styled-components+flow (浏览器需要用比较新版的chrome) 后端egg+mongodb+redis+socket。。基本把各个简单例子撸了个编,还是挺有收获的。~

1520860687855.jpg1520861191654.jpglogin.jpgjoin.jpg

egg skill

basic cover

  • [x] controller
  • [x] service
  • [x] model
  • [x] plugin
  • [x] extend
  • [x] logger
  • [ ] validate params
  • [ ] test

Advanced

  • [x] middleware --check(not)Login and spa redirect
  • [x] socket.io
  • [x] custom plugin –egg-mongolass
  • [x] deploy
  • [ ] err-handler

在线预览前端后端

前端算法收集库

$
0
0

algorithm.jpeg

1. 前言

前端算法代码收集库

旨在帮助大家提高javascript编码水平,代码规范,面对面试官问最难的算法问题也能从容应对

这是一个常见的js算法面试题收集库,包含测试,欢迎start,如果库中没有的算法,欢迎提issue或者PR,补全。

提到算法,这里就要说下时间复杂度。 时间复杂度:算法的时间复杂度是一个函数,描述了算法的运行时间。时间复杂度越低,效率越高。

2. 关于代码规范

俗话说,无规矩不成方圆,所以平时一定要养成良好的编码习惯

3. 关于代码测试

学习测试和持续集成(Continuous Integration,简称CI,意思是,在一个项目中,任何人对代码库的任何改动,都会触发CI服务器自动对项目进行构建,自动运行测试,甚至自动部署到测试环境。这样做的好处就是,随时发现问题,随时修复。因为修复问题的成本随着时间的推移而增长,越早发现,修复成本越低)。

4. 常见算法

4.1 二分查找

算法介绍

二分法查找,也称折半查找,是一种在有序数组中查找特定元素的搜索算法。查找过程可以分为以下步骤: (1)首先,从有序数组的中间的元素开始搜索,如果该元素正好是目标元素(即要查找的元素),则搜索过程结束,否则进行下一步。 (2)如果目标元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半区域查找,然后重复第一步的操作。 (3)如果某一步数组为空,则表示找不到目标元素。

参考代码:

非递归算法

function binary_search(arr,key){
  var low=0,
  high=arr.length-1;
  while(low<=high){
     var mid=parseInt((high+low)/2);
     if(key==arr[mid]){
        return mid;
     }else if(key>arr[mid]){
        low=mid+1;
     }else if(key<arr[mid]){
        high=mid-1;
    }else{
      return -1;
    }
  }
};
var arr=[1,2,3,4,5,6,7,8,9,10,11,23,44,86];
var result=binary_search(arr,10);
alert(result); // 9 返回目标元素的索引值

递归算法

function binary_search(arr,low,high,key){
  if(low>high){
    return -1;   
  }
  var mid=parseInt((high+low)/2);
  if(arr[mid]==key){
    return mid;
  }else if(arr[mid]>key){
    high=mid-1;
    return binary_search(arr,low,high,key);
  }else if(arr[mid]<key){
    low=mid+1;
    return binary_search(arr,low,high,key);
  }
};
var arr=[1,2,3,4,5,6,7,8,9,10,11,23,44,86];
var result=binary_search(arr,0,13,10);
alert(result); // 9 返回目标元素的索引值

4.2 排序

4.2.1 冒泡排序

算法介绍

解析:

  1. 比较相邻的两个元素,如果前一个比后一个大,则交换位置。
  2. 第一轮的时候最后一个元素应该是最大的一个。
  3. 按照步骤一的方法进行相邻两个元素的比较,这个时候由于最后一个元素已经是最大的了,所以最后一个元素不用比较。

js代码实现

function bubble_sort(arr){
  for(var i=0;i<arr.length-1;i++){
    for(var j=0;j<arr.length-i-1;j++){
      if(arr[j]>arr[j+1]){
        var swap=arr[j];
        arr[j]=arr[j+1];
        arr[j+1]=swap;
      }
    }
  }
}

var arr=[3,1,5,7,2,4,9,6,10,8];
bubble_sort(arr);
console.log(arr);
4.2.2快速排序

js代码实现解析:快速排序是对冒泡排序的一种改进,第一趟排序时将数据分成两部分,一部分比另一部分的所有数据都要小。然后递归调用,在两边都实行快速排序。

function quick_sort(arr){
  if(arr.length<=1){
    return arr;
  }
  var pivotIndex=Math.floor(arr.length/2);
  var pivot=arr.splice(pivotIndex,1)[0];

  var left=[];
  var right=[];
  for(var i=0;i<arr.length;i++){
    if(arr[i]<pivot){
      left.push(arr[i]);
    }else{
      right.push(arr[i]);
    }
  }

  return quick_sort(left).concat([pivot],quick_sort(right));
}

var arr=[5,6,2,1,3,8,7,1,2,3,4,7];
console.log(quick_sort(arr));
4.2.3 插入排序

算法介绍

解析:

  1. 从第一个元素开始,该元素可以认为已经被排序
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描
  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
  5. 将新元素插入到下一位置中
  6. 重复步骤2

js代码实现

function insert_sort(arr){
  var i=1,
  j,key,len=arr.length;
  for(;i<len;i++){
    var j=i;
    var key=arr[j];
    while(--j>-1){
      if(arr[j]>key){
        arr[j+1]=arr[j];
      }else{
        break;
      }
    }

    arr[j+1]=key;
  }

  return arr;
}

insert_sort([2,34,54,2,5,1,7]);

5. 最后

这个库暂时只收集了很小的一部分,欢迎留言或者提issue或者PR补充常见算法,让更多的人学习。

Viewing all 14821 articles
Browse latest View live


Latest Images