zoukankan      html  css  js  c++  java
  • 神奇的nextTick

    在nodejs中,process.nextTick()可以说是个绕不开的地方,不然你都不好意思跟别人说你已经懂了nodejs的异步非阻塞特性了。

    简介

    首先开听听nodejs中对nextTick的解释:

    On the next loop around the event loop call this callback. This is not a simple alias to setTimeout(fn, 0), it's much more efficient

    可以清楚得看到,nextTick()达到的效果跟setTimeout(fn, 0)是一样,但为什么nextTick()会比setTimeout()更高效,不知道大家有没有想多这样的问题。

    回答这个问题之前,大家要搞清楚nodejs中Tick的概念:在libev中的event loop 的每次迭代,在nodejs 中就叫做 “Tick”。javascript是单进程运行的,具体到nodejs中,就是如图运行。

     

    如果用nextTick()话,它的执行时间是O(1), 而setTimeout(fn, 0)则是O(n),考虑到篇幅,这里不再做具体的详述,有兴趣的朋友可以看《nodejs 异步之 Timer &Tick; 篇》一文,里面做了具体的解释。

    应用场景

    通过之前的介绍,可以看到nextTick()就是将某个任务放到下一个Tick中执行,简单讲就是延迟执行,那具体什么场景下需要用到这个功能呢?

    一. 同步emit时间,保证时序

    比如以下这段代码:

    var EventEmitter = require('events').EventEmitter;
    
    function StreamLibrary(resourceName) { 
        this.emit('start');
        // read from the file, and for every chunk read, do: 
           this.emit('data', chunkRead); 
    }
    StreamLibrary.prototype.__proto__ = EventEmitter.prototype;
    
    var stream = new StreamLibrary('fooResource');
    stream.on('start', function() {
        console.log('Reading has started');
    });
    
    stream.on('data', function(chunk) {
        console.log('Received: ' + chunk);
    });

    从代码本身来看没什么问题,但事实是:我们可能永远也监听不到"start"事件,因为这个事件在调用构造函数时,已经马上抛出了这个事件。

    那解决方案就是让'start'延时执行,具体如下:

    function StreamLibrary(resourceName) { 
       var self = this;
       process.nextTick(function() {
         self.emit('start');
    });
    
       // read from the file, and for every chunk read, do: 
           this.emit('data', chunkRead); 
    }

    其实早nodejs的源码也存在大量这样的用法,比如net模块

        require('dns').lookup(host, function(err, ip, addressType) {
          // It's possible we were destroyed while looking this up.
          // XXX it would be great if we could cancel the promise returned by
          // the look up.
          if (!self._connecting) return;
    
          if (err) {
            // net.createConnection() creates a net.Socket object and
            // immediately calls net.Socket.connect() on it (that's us).
            // There are no event listeners registered yet so defer the
            // error event to the next tick.
            process.nextTick(function() {
              self.emit('error', err);
              self._destroy();
            });
          }

    这里的err处理就是出于这样的目的。

    具体代码连接:https://github.com/joyent/node/blob/master/lib/net.js#L806

    二. cpu高密集代码段

    由于javascript是单进程的,当一个cpu被一个任务任务跑满时,它是不能执行其他任务的,在web服务中,这点甚至是致命,如果它被一个cpu密集的任务占满,那就不能相应其他的请求了,程序相当于“假死”。

    这个时候,将这个任务推迟执行倒不失为一个不错的选择。

    比如:

    var http = require('http');
    
    function compute() {
       // 执行一个cpu密集的任务
       // ...
       process.nextTick(compute);
    }
    
    http.createServer(function(req, res) {
       res.writeHead(200, {'Content-Type': 'text/plain'});
       res.end('Hello World');
    }).listen(5000, '127.0.0.1');
    
    compute();

    但问题又来,我们怎么去界定哪些任务是需要推迟推迟执行,而那些则不需要,有什么量化的标准吗?

    对不起,我暂时也没有特别好的方法。执行时间是个很好的判断标准,到底是1毫秒,1秒,还是10秒.......,并有很好的量化标准

    所以我的感受是:在你懂得nextTick的原理后,根据自己的业务场景来决定。

    经典文章:
    Understanding process.nextTick()
    nextTick and setTimeout
    test case about nextTick and setTimeout

  • 相关阅读:
    Vscode开发工具中的Simple React Snippets插件,对React开发有哪些便捷
    函数防抖和节流
    4.怎么样用CSS实现一个loading效果
    3.常见清除浮动的
    2.css处理各种溢出
    1. css画三角形
    2.javascript中call()和apply()区别
    1.js的继承的实现方法
    css、js小技巧
    JS函数声明和函数表达式的关系
  • 原文地址:https://www.cnblogs.com/lengyuhong/p/2987745.html
Copyright © 2011-2022 走看看