setImmediate so với nextTick


336

Phiên bản Node.js 0.10 đã được phát hành hôm nay và được giới thiệu setImmediate. Các thay đổi API tài liệu gợi ý sử dụng nó khi thực hiện đệ quy nextTickcác cuộc gọi.

Từ những gì MDN nói có vẻ rất giống với process.nextTick.

Khi nào tôi nên sử dụng nextTickvà khi nào nên sử dụng setImmediate?


20
Có 5 đoạn về sự thay đổi này trên blog blog.nodejs.org/2013/03/11/node-v0-10-0- ổn định
mak

1
Từ điểm chuẩn hiệu suất, có vẻ như nextTicknhanh hơn so với setImmediatecác tính toán lớn.

10
Đối với hồ sơ, tôi đã đọc năm đoạn đó trước và vẫn kết thúc câu hỏi này khi nó không thực sự rõ ràng cho tôi điều gì. Câu trả lời được chấp nhận ngắn gọn hơn nhiều và thực sự mô tả những gì setImmediatelàm chi tiết hơn.
Chev

Tôi đã giải thích sự khác biệt rất chi tiết trong blog của tôi .
plafer

Đây có phải là trường hợp mà GC có thể chạy trước setImmediate, nhưng không phải trước đó nextTick?

Câu trả lời:


510

Sử dụng setImmediatenếu bạn muốn xếp hàng chức năng đằng sau bất kỳ cuộc gọi lại sự kiện I / O nào đã có trong hàng đợi sự kiện. Sử dụng process.nextTickđể xếp hàng hiệu quả chức năng ở đầu hàng đợi sự kiện để nó thực thi ngay sau khi chức năng hiện tại hoàn thành.

Vì vậy, trong trường hợp bạn đang cố gắng phá vỡ một công việc chạy dài, bị ràng buộc bởi CPU bằng cách sử dụng đệ quy, bây giờ bạn sẽ muốn sử dụng setImmediatethay vì process.nextTickxếp hàng lặp lại tiếp theo vì nếu không, bất kỳ cuộc gọi lại sự kiện I / O nào cũng sẽ không có cơ hội để chạy giữa các lần lặp.


86
Các cuộc gọi lại được chuyển đến process.nextTick thường sẽ được gọi ở cuối luồng thực thi hiện tại và do đó nhanh như gọi một hàm một cách đồng bộ. Nếu không được chọn, điều này sẽ bỏ đói vòng lặp sự kiện, ngăn chặn bất kỳ I / O nào xảy ra. setImmediates được xếp hàng theo thứ tự được tạo và được bật ra khỏi hàng đợi một lần trên mỗi vòng lặp. Điều này khác với process.nextTick sẽ thực thi các cuộc gọi lại hàng đợi process.maxTickDepth trên mỗi lần lặp. setImmediate sẽ mang lại vòng lặp sự kiện sau khi thực hiện cuộc gọi lại được xếp hàng để đảm bảo I / O không bị bỏ đói.
Benjamin Gruenbaum

2
@UstamanSangat setImmediate chỉ được IE10 + hỗ trợ, tất cả các trình duyệt khác đều ngoan cố từ chối thực hiện tiêu chuẩn có khả năng trong tương lai vì chúng không thích bị Microsoft đánh bại. Để đạt được kết quả tương tự trong FF / Chrome, bạn có thể sử dụng postMessage (đăng một tin nhắn lên cửa sổ của riêng bạn). Bạn cũng có thể cân nhắc sử dụng requestAnimationFrame, đặc biệt nếu các cập nhật của bạn có liên quan đến UI. setTimeout (func, 0) hoàn toàn không hoạt động như process.nextTick.
fabspro

44
@fabspro "bởi vì họ không thích bị đánh bại Microsoft của tôi" khiến bạn nghe có vẻ đau lòng về điều gì đó. Nó chủ yếu là vì nó khủng khiếp, được đặt tên khủng khiếp. Nếu có một lần chức năng setImmediate sẽ không bao giờ chạy, nó sẽ ngay lập tức. Tên của hàm trái ngược hoàn toàn với những gì nó làm. nextTick và setImmediate sẽ tốt hơn khi chuyển đổi xung quanh; setImmediate thực thi ngay lập tức sau khi ngăn xếp hiện tại hoàn thành (trước khi chờ I / O) và nextTick thực thi ở cuối dấu tick tiếp theo (sau khi chờ I / O). Nhưng sau đó, điều này đã được nói hàng ngàn lần rồi.
Craig Andrew

4
@fabspro Nhưng thật không may, chức năng này được gọi là nextTick. nextTick thực thi "ngay lập tức" trong khi setImmediate giống như setTimeout / postMessage.
Robert

1
@CraigAndrews Tôi sẽ tránh requestAnimationFramevì nó không xảy ra (tôi chắc chắn đã thấy điều này, tôi nghĩ ví dụ là tab không phải là tab hiện tại) và nó có thể được gọi trước khi trang hoàn thành vẽ (tức là trình duyệt vẫn đang bận vẽ).
robocat

68

Như một minh họa

import fs from 'fs';
import http from 'http';

const options = {
  host: 'www.stackoverflow.com',
  port: 80,
  path: '/index.html'
};

describe('deferredExecution', () => {
  it('deferredExecution', (done) => {
    console.log('Start');
    setTimeout(() => console.log('TO1'), 0);
    setImmediate(() => console.log('IM1'));
    process.nextTick(() => console.log('NT1'));
    setImmediate(() => console.log('IM2'));
    process.nextTick(() => console.log('NT2'));
    http.get(options, () => console.log('IO1'));
    fs.readdir(process.cwd(), () => console.log('IO2'));
    setImmediate(() => console.log('IM3'));
    process.nextTick(() => console.log('NT3'));
    setImmediate(() => console.log('IM4'));
    fs.readdir(process.cwd(), () => console.log('IO3'));
    console.log('Done');
    setTimeout(done, 1500);
  });
});

sẽ cho đầu ra sau

Start
Done
NT1
NT2
NT3
TO1
IO2
IO3
IM1
IM2
IM3
IM4
IO1

Tôi hy vọng điều này có thể giúp hiểu được sự khác biệt.

Cập nhật:

Các cuộc gọi lại được hoãn lại process.nextTick()trước khi chạy bất kỳ sự kiện I / O nào khác, trong khi với setImmediate (), việc thực thi được xếp hàng sau bất kỳ sự kiện I / O nào đã có trong hàng đợi.

Các mẫu thiết kế Node.js , của Mario Casciaro (có lẽ là cuốn sách hay nhất về node.js / js)


2
Điều này thực sự hữu ích cảm ơn. Tôi nghĩ rằng hình ảnh và ví dụ là cách nhanh nhất để hiểu một cái gì đó.
John James

1
Tôi nghĩ điều quan trọng là chỉ ra rằng setTimeout () và setImmediate () khi không nằm trong chu kỳ I / O, thứ tự là không xác định tùy thuộc vào hiệu suất của một quy trình. nodejs.org/en/docs/guides/event-loop-timers-and-nexttick For example, if we run the following script which is not within an I/O cycle (i.e. the main module), the order in which the two timers are executed is non-deterministic, as it is bound by the performance of the process: Vì vậy, câu trả lời này không thực sự trả lời chính xác mà chỉ là một ví dụ có thể khác nhau trong bối cảnh khác nhau
Actung 30/10/18

Như được chỉ ra bởi @Actung. điều rất quan trọng là phải biết liệu setTimetout và setImmediate có nằm trong chu kỳ I / O hay không, để xác định kết quả.
Rajika Imal

50

Tôi nghĩ rằng tôi có thể minh họa điều này khá độc đáo. Vì nextTickđược gọi ở cuối hoạt động hiện tại, nên gọi nó một cách đệ quy có thể kết thúc việc chặn vòng lặp sự kiện tiếp tục. setImmediategiải quyết điều này bằng cách bắn trong giai đoạn kiểm tra của vòng lặp sự kiện, cho phép vòng lặp sự kiện tiếp tục bình thường.

   ┌───────────────────────┐
┌─>│        timers         
  └──────────┬────────────┘
  ┌──────────┴────────────┐
       I/O callbacks     
  └──────────┬────────────┘
  ┌──────────┴────────────┐
       idle, prepare     
  └──────────┬────────────┘      ┌───────────────┐
  ┌──────────┴────────────┐         incoming:   
           poll          │<─────┤  connections, 
  └──────────┬────────────┘         data, etc.  
  ┌──────────┴────────────┐      └───────────────┘
          check          
  └──────────┬────────────┘
  ┌──────────┴────────────┐
└──┤    close callbacks    
   └───────────────────────┘

nguồn: https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/

Lưu ý rằng giai đoạn kiểm tra là ngay sau giai đoạn thăm dò ý kiến. Điều này là do giai đoạn thăm dò ý kiến ​​và các cuộc gọi lại I / O là nơi có nhiều khả năng các cuộc gọi của bạn setImmediatesẽ chạy. Vì vậy, lý tưởng nhất là hầu hết các cuộc gọi đó sẽ thực sự ngay lập tức, không phải ngay lập tức như nextTickđược kiểm tra sau mỗi hoạt động và về mặt kỹ thuật tồn tại bên ngoài vòng lặp sự kiện.

Chúng ta hãy xem một ví dụ nhỏ về sự khác biệt giữa setImmediateprocess.nextTick:

function step(iteration) {
  if (iteration === 10) return;
  setImmediate(() => {
    console.log(`setImmediate iteration: ${iteration}`);
    step(iteration + 1); // Recursive call from setImmediate handler.
  });
  process.nextTick(() => {
    console.log(`nextTick iteration: ${iteration}`);
  });
}
step(0);

Giả sử chúng ta vừa chạy chương trình này và đang thực hiện bước lặp đầu tiên của vòng lặp sự kiện. Nó sẽ gọi vào stephàm với số lần lặp bằng không. Sau đó, nó sẽ đăng ký hai xử lý, một cho setImmediatevà một cho process.nextTick. Sau đó, chúng tôi gọi đệ quy hàm này từ setImmediatetrình xử lý sẽ chạy trong giai đoạn kiểm tra tiếp theo. Trình nextTickxử lý sẽ chạy ở cuối hoạt động hiện tại làm gián đoạn vòng lặp sự kiện, vì vậy mặc dù đã được đăng ký lần thứ hai, nó sẽ thực sự chạy trước.

Thứ tự kết thúc là: nextTickkích hoạt khi hoạt động hiện tại kết thúc, vòng lặp sự kiện tiếp theo bắt đầu, các giai đoạn vòng lặp sự kiện thông thường thực thi, setImmediatekích hoạt và gọi đệ quy stepchức năng của chúng tôi để bắt đầu lại quá trình. Hoạt động hiện tại kết thúc, nextTickhỏa hoạn, vv

Đầu ra của đoạn mã trên sẽ là:

nextTick iteration: 0
setImmediate iteration: 0
nextTick iteration: 1
setImmediate iteration: 1
nextTick iteration: 2
setImmediate iteration: 2
nextTick iteration: 3
setImmediate iteration: 3
nextTick iteration: 4
setImmediate iteration: 4
nextTick iteration: 5
setImmediate iteration: 5
nextTick iteration: 6
setImmediate iteration: 6
nextTick iteration: 7
setImmediate iteration: 7
nextTick iteration: 8
setImmediate iteration: 8
nextTick iteration: 9
setImmediate iteration: 9

Bây giờ, hãy chuyển cuộc gọi đệ quy stepcủa chúng tôi sang nextTickxử lý thay vì setImmediate.

function step(iteration) {
  if (iteration === 10) return;
  setImmediate(() => {
    console.log(`setImmediate iteration: ${iteration}`);
  });
  process.nextTick(() => {
    console.log(`nextTick iteration: ${iteration}`);
    step(iteration + 1); // Recursive call from nextTick handler.
  });
}
step(0);

Bây giờ chúng tôi đã chuyển cuộc gọi đệ quy stepsang nextTickxử lý, mọi thứ sẽ hoạt động theo một trật tự khác. Lặp lại đầu tiên của chúng tôi về vòng lặp sự kiện chạy và gọi stepđăng ký một setImmedaitetrình xử lý cũng như một nextTicktrình xử lý. Sau khi hoạt động hiện tại kết thúc, nextTicktrình xử lý của chúng tôi sẽ thực hiện các cuộc gọi đệ quy stepvà đăng ký một setImmediatetrình xử lý khác cũng như một nextTicktrình xử lý khác . Vì nextTicktrình xử lý kích hoạt sau thao tác hiện tại, việc đăng ký nextTicktrình xử lý trong nextTicktrình xử lý sẽ khiến trình xử lý thứ hai chạy ngay sau khi thao tác xử lý hiện tại kết thúc. Các nextTicktrình xử lý sẽ tiếp tục bắn, ngăn chặn vòng lặp sự kiện hiện tại tiếp tục. Chúng tôi sẽ vượt qua tất cảnextTickxử lý trước khi chúng ta thấy một setImmediateđám cháy xử lý duy nhất .

Đầu ra của đoạn mã trên kết thúc là:

nextTick iteration: 0
nextTick iteration: 1
nextTick iteration: 2
nextTick iteration: 3
nextTick iteration: 4
nextTick iteration: 5
nextTick iteration: 6
nextTick iteration: 7
nextTick iteration: 8
nextTick iteration: 9
setImmediate iteration: 0
setImmediate iteration: 1
setImmediate iteration: 2
setImmediate iteration: 3
setImmediate iteration: 4
setImmediate iteration: 5
setImmediate iteration: 6
setImmediate iteration: 7
setImmediate iteration: 8
setImmediate iteration: 9

Lưu ý rằng chúng tôi đã không làm gián đoạn cuộc gọi đệ quy và hủy cuộc gọi đó sau 10 lần lặp thì các nextTickcuộc gọi sẽ tiếp tục đệ quy và không bao giờ để vòng lặp sự kiện tiếp tục sang giai đoạn tiếp theo. Đây là cách nextTickcó thể trở thành chặn khi được sử dụng đệ quy trong khi setImmediatesẽ kích hoạt vòng lặp sự kiện tiếp theo và thiết lập một setImmediatetrình xử lý khác từ bên trong sẽ không làm gián đoạn vòng lặp sự kiện hiện tại, cho phép nó tiếp tục thực hiện các giai đoạn của vòng lặp sự kiện như bình thường.

Mong rằng sẽ giúp!

Tái bút - Tôi đồng ý với các nhà bình luận khác rằng tên của hai hàm có thể dễ dàng hoán đổi vì nextTickâm thanh như phát ra trong vòng sự kiện tiếp theo thay vì kết thúc vòng lặp hiện tại và kết thúc vòng lặp hiện tại là "ngay lập tức "Hơn phần đầu của vòng lặp tiếp theo. Ồ, đó là những gì chúng ta nhận được khi đáo hạn API và mọi người phụ thuộc vào các giao diện hiện có.


2
Mô tả khá rõ ràng. Tôi nghĩ rằng câu trả lời này cần nhiều upvote.
Actung

Giải thích rõ (Y)
Dhiraj Sharma

điều quan trọng là phải nhắc lại cảnh báo của Node về việc sử dụng process.nextTick. Nếu bạn yêu cầu một số lượng lớn các cuộc gọi lại trong nextTickQueue, bạn có khả năng có thể bỏ đói vòng lặp sự kiện bằng cách đảm bảo giai đoạn thăm dò ý kiến ​​không bao giờ đạt được. Đây là lý do tại sao bạn thường thích setImmediate.
trò hề

1
Cảm ơn, đây là lời giải thích tốt nhất. mã mẫu thực sự hữu ích.
skyhavoc

@skyhavoc rất vui vì tôi có thể giúp!
Chev

30

Trong các bình luận trong câu trả lời, không nói rõ rằng nextTick đã chuyển từ Macrosemantics sang microsemantics.

trước khi nút 0.9 (khi setImmediate được giới thiệu), nextTick hoạt động khi bắt đầu cuộc gọi tiếp theo.

kể từ nút 0.9, nextTick hoạt động ở cuối của nút gọi hiện có, trong khi setImmediate nằm ở đầu của nút gọi tiếp theo

hãy xem https://github.com/YuzuJS/setImmediate để biết các công cụ và chi tiết


11

Nói một cách đơn giản, process.NextTick () sẽ được thực thi ở lần đánh dấu tiếp theo của vòng lặp sự kiện. Tuy nhiên, về cơ bản, setImmediate, có một pha riêng biệt, đảm bảo rằng cuộc gọi lại được đăng ký theo setImmediate () sẽ chỉ được gọi sau giai đoạn gọi lại và bỏ phiếu IO.

Vui lòng tham khảo liên kết này để được giải thích hay: https://medium.com/the-node-js-collection/what-you-should-ledge-to-really-understand-the-node-js-event-loop-and -its-metrics-c4907b19da4c

sự kiện vòng lặp sự kiện đơn giản hóa


7

Một số câu trả lời tuyệt vời ở đây chi tiết cách cả hai làm việc.

Chỉ cần thêm một câu trả lời cho câu hỏi cụ thể:

Khi nào tôi nên sử dụng nextTickvà khi nào nên sử dụng setImmediate?


Luôn luôn sử dụng setImmediate.


Các Node.js Event Loop, Timers, vàprocess.nextTick() doc bao gồm những điều sau đây:

Chúng tôi khuyên các nhà phát triển nên sử dụng setImmediate()trong mọi trường hợp vì lý do dễ dàng hơn (và nó dẫn đến mã tương thích với nhiều môi trường khác nhau, như trình duyệt JS.)


Trước đó trong tài liệu, nó cảnh báo process.nextTickcó thể dẫn đến ...

một số tình huống xấu bởi vì nó cho phép bạn "bỏ đói" I / O của mình bằng cách thực hiện các process.nextTick()cuộc gọi đệ quy , điều này ngăn vòng lặp sự kiện đến giai đoạn thăm dò ý kiến .

Hóa ra, process.nextTickthậm chí có thể chết đói Promises:

Promise.resolve().then(() => { console.log('this happens LAST'); });

process.nextTick(() => {
  console.log('all of these...');
  process.nextTick(() => {
    console.log('...happen before...');
    process.nextTick(() => {
      console.log('...the Promise ever...');
      process.nextTick(() => {
        console.log('...has a chance to resolve');
      })
    })
  })
})

Mặt khác, setImmediate" dễ lý luận hơn " và tránh các loại vấn đề này:

Promise.resolve().then(() => { console.log('this happens FIRST'); });

setImmediate(() => {
  console.log('this happens LAST');
})

Vì vậy, trừ khi có một nhu cầu cụ thể đối với hành vi duy nhất của process.nextTick, phương pháp được đề xuất là " sử dụng setImmediate()trong mọi trường hợp ".


1

Tôi khuyên bạn nên kiểm tra phần tài liệu dành riêng cho Loop để hiểu rõ hơn. Một số đoạn được lấy từ đó:

Chúng tôi có hai cuộc gọi tương tự như liên quan đến người dùng, nhưng tên của họ gây nhầm lẫn.

  • process.nextTick () kích hoạt ngay lập tức trên cùng một pha

  • setImmediate () kích hoạt lần lặp sau hoặc 'tick' của
    vòng lặp sự kiện

Về bản chất, các tên nên được hoán đổi. process.nextTick () kích hoạt ngay lập tức hơn setImmediate (), nhưng đây là một tạo tác của quá khứ không có khả năng thay đổi.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.