如何在不影响其他线程的情况下休眠node.js中的线程?


74

根据了解node.js事件循环,node.js支持单线程模型。这意味着,如果我对node.js服务器发出多个请求,它将不会为每个请求生成一个新线程,而是将一个接一个地执行每个请求。这意味着,如果我对node.js代码中的第一个请求执行以下操作,同时在节点上有一个新请求进入,则第二个请求必须等待直到第一个请求完成,包括5秒的睡眠时间。对?

var sleep = require('sleep');
    sleep.sleep(5)//sleep for 5 seconds

有没有一种方法,node.js可以为每个请求生成一个新线程,从而使第二个请求不必等待第一个请求完成,或者我只能在特定线程上调用sleep吗?


4
您不需要那样睡觉。您应该看看setTimeout-> nodejs.org/api/globals.html#globals_settimeout_cb_ms
Alfred

Answers:


157

如果您指的是npm模块sleep,它将在自述文件中注明sleep将阻止执行。所以您是对的-这不是您想要的。相反,您想使用非阻塞的setTimeout。这是一个例子:

setTimeout(function() {
  console.log('hello world!');
}, 5000);

对于希望使用es7 async / await进行此操作的任何人,此示例都应有所帮助:

const snooze = ms => new Promise(resolve => setTimeout(resolve, ms));

const example = async () => {
  console.log('About to snooze without halting the event loop...');
  await snooze(1000);
  console.log('done!');
};

example();

因此,node.js将一次执行一个请求,这意味着它遵循单线程模型。对?
艾莉2012年

7
是的,它使用单个执行线程,因此它一次只能计算一个请求,但是可能有许多请求挂起的事件。调用时setTimeout,它将事件存储在队列中,然后可以开始执行下一个请求。五秒钟后,该事件将从队列中删除,并将执行回调(console.log在此情况下包含的函数)。
David Weldon,2012年

请看一下此截屏视频,以获取有关Node.js如何运行多个请求而不使它们彼此“等待”的更多详细信息。
米歇尔·提里(Michelle Tilley)2012年

6
它不会阻塞线程。setTimeout之后的代码将继续运行,然后在5秒钟后控制台打印“ hello world”。
CodeFarmer 2014年

1
@DavidWeldon,从队列中删除setTimeout后,Node如何确定应在某个时间点将其触发?换句话说,如果该功能不在执行模式/上下文中,那么什么触发该功能在五秒钟后运行?
securecurve

1

如果每个循环中都有一个异步请求,并且每个请求之间都需要一定的时间,则可以使用以下代码:

   var startTimeout = function(timeout, i){
        setTimeout(function() {
            myAsyncFunc(i).then(function(data){
                console.log(data);
            })
        }, timeout);
   }

   var myFunc = function(){
        timeout = 0;
        i = 0;
        while(i < 10){
            // By calling a function, the i-value is going to be 1.. 10 and not always 10
            startTimeout(timeout, i);
            // Increase timeout by 1 sec after each call
            timeout += 1000;
            i++;
        }
    }

此示例在每个请求之后等待1秒,然后发送下一个请求。


-1。这不会做您期望的事情。循环不会异步运行。它将立即触发所有请求。考虑使用异步循环时要非常小心-这种组合很棘手。
treecoder '17

Of course the loop gets called all at once. But the timout increases each loop. So 1 sec... 2 sec ... 3 sec. That's why "myAsyncFunc" will be called each second rather than all at once. It works as expected when I teste it. Can you tell me what you think is wrong with it?
Jodo

1
The code DOES work, by the way, in a little tricky way. The startTimeout function is called 10 times immediately, but each call has a timeout one second bigger, so every call to myAsyncFunc is called one second later.
greuze

1
But that defeats the whole purpose of the loop. You should be able to decide how many times the loop runs. What you are doing above will execute the loop exactly 10 times -- no matter whether you needed 10 iterations or not. You can not decide whether you want another iteration or not at the end of the previous iteration -- because you don't know when will the end of previous iteration happen. Hence you have to decide in advance how many iterations will occur. The iteration breaking logic will then leak into the subject callback -- myAsyncFunc in your case.
treecoder

1
The second flaw is that you are assuming that your callback is going to take not more than 1 second to execute. What if your next iteration depended on your previous iteration's result, and that result was not guaranteed to be available within 1 second. Then your next iteration will start without the needed result -- because it will start exactly 1 second after the previous iteration, paying no attention to whether the callback invoked from the previous iteration has actually concluded or not.
treecoder

1

Please consider the deasync module, personally I don't like the Promise way to make all functions async, and keyword async/await anythere. And I think the official node.js should consider to expose the event loop API, this will solve the callback hell simply. Node.js is a framework not a language.

var node = require("deasync");
node.loop = node.runLoopOnce;

var done = 0;
// async call here
db.query("select * from ticket", (error, results, fields)=>{
    done = 1;
});

while (!done)
    node.loop();

// Now, here you go

0

When working with async functions or observables provided by 3rd party libraries, for example Cloud firestore, I've found functions the waitFor method shown below (TypeScript, but you get the idea...) to be helpful when you need to wait on some process to complete, but you don't want to have to embed callbacks within callbacks within callbacks nor risk an infinite loop.

This method is sort of similar to a while (!condition) sleep loop, but yields asynchronously and performs a test on the completion condition at regular intervals till true or timeout.

export const sleep = (ms: number) => {
    return new Promise(resolve => setTimeout(resolve, ms))
}
/**
 * Wait until the condition tested in a function returns true, or until 
 * a timeout is exceeded.
 * @param interval The frenequency with which the boolean function contained in condition is called.
 * @param timeout  The maximum time to allow for booleanFunction to return true
 * @param booleanFunction:  A completion function to evaluate after each interval. waitFor will return true as soon as the completion function returns true.   
 */
export const waitFor = async function (interval: number, timeout: number,
    booleanFunction: Function): Promise<boolean> {
    let elapsed = 1;
    if (booleanFunction()) return true;
    while (elapsed < timeout) {
        elapsed += interval;
        await sleep(interval);
        if (booleanFunction()) {
            return true;
        }
    }
    return false;
}

The say you have a long running process on your backend you want to complete before some other task is undertaken. For example if you have a function that totals a list of accounts, but you want to refresh the accounts from the backend before you calculate, you can do something like this:

async recalcAccountTotals() : number {
     this.accountService.refresh();   //start the async process.
     if (this.accounts.dirty) {
           let updateResult = await waitFor(100,2000,()=> {return !(this.accounts.dirty)})
     }
 if(!updateResult) { 
      console.error("Account refresh timed out, recalc aborted");
      return NaN;
    }
 return ... //calculate the account total. 
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.