SlideShare a Scribd company logo
Disclaimer ;-)
Doesn't necessarily reflect the views of IBM, or any of
my Node.js collaborators...
1
nextTick
•  [ ] in next tick of the event loop
•  [ ] immediately after A returns
2
process.nextTick(function() {
// Called ...
});
nextTick
•  [ ] in next tick of the event loop
•  [x] immediately after A returns
3
process.nextTick(function() {
// Called ...
});
setImmediate
•  [ ] in next tick of the event loop
•  [ ] immediately after A returns
4
setImmediate(function() {
// Called ...
});
setImmediate
•  [x] in next tick of the event loop
•  [ ] immediately after A returns
5
setImmediate(function() {
// Called ...
});
nextTick vs setImmediate
•  nextTick: adds callback to queue to be called immediately
•  setImmediate: adds callback to queue to be called next tick
6
worker.kill()
7
var cluster = require('cluster');
if (cluster.isMaster) {
cluster.fork()
.on('online', function() {
this.kill('SIGHUP');
})
.on('exit', function() {
console.log('Exit');
});
} else {
process.on('SIGHUP', function() {
console.log('Hup');
});
}
worker.kill()
•  [ ] "Hup"
•  [ ] "Exit"
•  [ ] "Hup" and "Exit"
•  [ ] No output
8
var cluster = require('cluster');
if (cluster.isMaster) {
cluster.fork()
.on('online', function() {
this.kill('SIGHUP');
})
.on('exit', function() { console.log('Exit'); });
} else {
process.on('SIGHUP', function() { console.log('Hup'); });
}
worker.kill()
•  [ ] "Hup"
•  [x] "Exit"
•  [ ] "Hup" and "Exit"
•  [ ] No output
9
var cluster = require('cluster');
if (cluster.isMaster) {
cluster.fork()
.on('online', function() {
this.kill('SIGHUP');
})
.on('exit', function() { console.log('Exit'); });
} else {
process.on('SIGHUP', function() { console.log('Hup'); });
}
worker.kill() - what is it really?
•  Use worker.process.kill() if you want to signal the worker
•  Asymetric with worker.send(), worker.on('message', ...), etc.
10
Worker.prototype.kill = function(signo) {
var proc = this.process;
this.once('disconnect', function() {
this.process.kill(signo || 'SIGTERM');
});
this.disconnect();
};
worker.suicide - kill the worker
•  [ ] true
•  [ ] false
11
var cluster = require('cluster');
if (cluster.isMaster) {
cluster.fork()
.on('online', function() {
this.kill('SIGTERM');
})
.on('exit', function() {
// this.suicide is ....
});
} else {
}
worker.suicide - kill the worker
•  [x] true
•  [ ] false
12
var cluster = require('cluster');
if (cluster.isMaster) {
cluster.fork()
.on('online', function() {
this.kill('SIGTERM');
})
.on('exit', function() {
// this.suicide is ....
});
} else {
}
worker.suicide - exit the worker
•  [ ] true
•  [ ] false
13
var cluster = require('cluster');
if (cluster.isMaster) {
cluster.fork()
.on('exit', function() {
// this.suicide is ....
});
} else {
process.exit(0);
}
worker.suicide - exit the worker
•  [ ] true
•  [x] false
14
var cluster = require('cluster');
if (cluster.isMaster) {
cluster.fork()
.on('exit', function() {
// this.suicide is ....
});
} else {
process.exit(0);
}
worker.suicide - what does it mean?
•  worker.disconnect() in parent (implicitly done by worker.kill())
•  process.disconnect() in worker
15
Actual meaning: "orderly exit"
url.parse/format
16
var url = require('url');
var uri = url.parse('http://example.com:8080/path');
uri.host = 'ibm.com';
console.log(url.format(uri));
•  [ ] yes
•  [ ] no
Prints http://ibm.com:8080/path ...
url.parse/format
17
var url = require('url');
var uri = url.parse('http://example.com:8080/path');
uri.host = 'ibm.com';
console.log(url.format(uri));
•  [ ] yes
•  [x] no: http://ibm.com/path
Prints http://ibm.com:8080/path ...
url.parse/format
•  host is "example.com:8080"
•  hostname is "example.com"
18
url.parse/format
19
var url = require('url');
var uri = url.parse('http://example.com:8080/path');
uri.hostname = 'ibm.com';
console.log(url.format(uri));
•  [ ] yes
•  [ ] no
Prints http://ibm.com:8080/path ...
url.parse/format
20
var url = require('url');
var uri = url.parse('http://example.com:8080/path');
uri.hostname = 'ibm.com';
console.log(url.format(uri));
•  [ ] yes
•  [x] no: http://example.com:8080/path
Prints http://ibm.com:8080/path ...
url.parse/format
•  hostname and port are ignored if host is present...
21
url.parse/format
22
var url = require('url');
var uri = url.parse('http://example.com:8080/path');
delete uri.host;
uri.hostname = 'ibm.com';
console.log(url.format(uri));
•  http://ibm.com:8080/path, finally!
url.parse/format
•  parse: "http://example:8080":
– port is 8080
– host is "example:8080"... why not hostport?
– hostname is "example"... why not host?
•  format: hostname and port are ignored if hostname is present
– shouldn't they be prefered if present?
23
path.parse/format
24
var path = require('path');
var bits = path.parse('some/dir/index.txt');
// has: .name, .ext
bits.ext = '.html';
console.log(path.format(bits));
Logs some/dir/index.html...
•  [ ] yes
•  [ ] no
path.parse/format
25
var path = require('path');
var bits = path.parse('some/dir/index.txt');
bits.ext = '.html';
console.log(path.format(bits));
Logs some/dir/index.html...
•  [ ] yes
•  [x] no: some/dir/index.txt
path.parse/format
26
var path = require('path');
var bits = path.parse('some/dir/index.txt');
console.log(bits.base); // > index.txt
delete bits.base
bits.ext = '.html';
console.log(path.format(bits));
Logs some/dir/index.html...
•  [ ] yes
•  [ ] no
Its probably like url...
path.parse/format
27
var path = require('path');
var bits = path.parse('some/dir/index.txt');
console.log(bits.base); // > index.txt
delete bits.base
bits.ext = '.html';
console.log(path.format(bits));
Logs some/dir/index.html...
•  [ ] yes
•  [x] no: some/dir/
path.parse/format
28
var path = require('path');
var bits = path.parse('some/dir/index.txt');
bits.base = bits.name + '.html';
console.log(path.format(bits)); // > some/dir/index.html
Its just completely different, format always ignores name and ext.
Correct:
streams v1, 2, 3
29
Note that, for backwards compatibility reasons, removing 'data' event
handlers will not automatically pause the stream. Also, if there are
piped destinations, then calling pause() will not guarantee that the
stream will remain paused once those destinations drain and ask for
more data.
From https://nodejs.org/api/stream.html#stream_class_stream_readable:
streams v1, 2, 3
30
Note that, for backwards compatibility reasons, removing 'data' event
handlers will not automatically pause the stream. Also, if there are
piped destinations, then calling pause() will not guarantee that the
stream will remain paused once those destinations drain and ask for
more data.
From https://nodejs.org/api/stream.html#stream_class_stream_readable:
When can we delete backwards compat to v0.8?
Question
•  Laurie Voss (@seldo), NPM, Nov. 28, 2015
31
What kind of node do you want: a good one?
or a (mostly) v0.8 compatible one?
@octetcloud As of right now, 30.4% of registered npm
accounts are less than 6 months old.
Answer
•  github, twitter, etc..
•  Be tolerant (or intolerant) of breakages, but what
you say will effect what happens.
32
Its yours (and a lot of other people's) Node.js, speak out:
Flames to:
•  github: @sam-github
•  email: rsam@ca.ibm.com
•  twitter: @octetcloud
•  talk source: https://gist.github.com/sam-github/4c5c019b92cf95fb6571, or
https://goo.gl/2RWpqE
33

More Related Content

PDF
Coscup2021-rust-toturial
PDF
Coscup2021 - useful abstractions at rust and it's practical usage
PPTX
Linux kernel debugging
PDF
C++ game development with oxygine
PDF
Антон Нонко, Классические строки в C++
PDF
Обзор фреймворка Twisted
PPTX
Ricky Bobby's World
PDF
Elixir @ Paris.rb
Coscup2021-rust-toturial
Coscup2021 - useful abstractions at rust and it's practical usage
Linux kernel debugging
C++ game development with oxygine
Антон Нонко, Классические строки в C++
Обзор фреймворка Twisted
Ricky Bobby's World
Elixir @ Paris.rb

What's hot (20)

PDF
Kirk Shoop, Reactive programming in C++
PDF
Stupid Awesome Python Tricks
PPTX
Fact, Fiction, and FP
PPTX
CONFidence 2015: DTrace + OSX = Fun - Andrzej Dyjak
PPTX
Node.js System: The Landing
PPTX
Groovy
PPTX
Самые вкусные баги из игрового кода: как ошибаются наши коллеги-программисты ...
PDF
Rust: код может быть одновременно безопасным и быстрым, Степан Кольцов
PPT
Евгений Крутько, Многопоточные вычисления, современный подход.
PDF
Rootkit on Linux X86 v2.6
PDF
20140531 serebryany lecture02_find_scary_cpp_bugs
PDF
Arduino coding class part ii
PDF
20140531 serebryany lecture01_fantastic_cpp_bugs
PDF
JavaScript Survival Guide
PPTX
PDF
Arduino coding class
PPTX
MiamiJS - The Future of JavaScript
PPTX
NSOperation objective-c
PDF
LLVM Backend の紹介
PDF
C++ L08-Classes Part1
Kirk Shoop, Reactive programming in C++
Stupid Awesome Python Tricks
Fact, Fiction, and FP
CONFidence 2015: DTrace + OSX = Fun - Andrzej Dyjak
Node.js System: The Landing
Groovy
Самые вкусные баги из игрового кода: как ошибаются наши коллеги-программисты ...
Rust: код может быть одновременно безопасным и быстрым, Степан Кольцов
Евгений Крутько, Многопоточные вычисления, современный подход.
Rootkit on Linux X86 v2.6
20140531 serebryany lecture02_find_scary_cpp_bugs
Arduino coding class part ii
20140531 serebryany lecture01_fantastic_cpp_bugs
JavaScript Survival Guide
Arduino coding class
MiamiJS - The Future of JavaScript
NSOperation objective-c
LLVM Backend の紹介
C++ L08-Classes Part1
Ad

Viewers also liked (13)

PDF
Aporte Pastoral. Adviento 2015. Año de la Misericordia
PPTX
Apply Property 2016 Roadshow Slides
PDF
New blank document
PPTX
Fixflo Scottish Property Technology Roadshow Slides
PPTX
Colegio 2
DOCX
Resumen de nulidad y simulacion
PDF
Open Data: What’s Next?
PPTX
международный университет (2)
PPTX
PRESENTACION ACEBRON
PDF
mohamed morsy_CV
PDF
Understanding the Single Thread Event Loop
PDF
Open Culture - How Wiki loves art and data - Packed
PPT
Estrategias de Prevención y manejo de la agresión escolar
Aporte Pastoral. Adviento 2015. Año de la Misericordia
Apply Property 2016 Roadshow Slides
New blank document
Fixflo Scottish Property Technology Roadshow Slides
Colegio 2
Resumen de nulidad y simulacion
Open Data: What’s Next?
международный университет (2)
PRESENTACION ACEBRON
mohamed morsy_CV
Understanding the Single Thread Event Loop
Open Culture - How Wiki loves art and data - Packed
Estrategias de Prevención y manejo de la agresión escolar
Ad

Similar to Node.js API pitfalls (20)

KEY
Playing With Fire - An Introduction to Node.js
PDF
"Node.js vs workers — A comparison of two JavaScript runtimes", James M Snell
PDF
Introduction to Node.js
PDF
Frontend Track NodeJS
PDF
Original slides from Ryan Dahl's NodeJs intro talk
PDF
NodeJS for Beginner
PDF
Node, can you even in CPU intensive operations?
PDF
Node.js Event Loop & EventEmitter
PDF
About Node.js
KEY
A language for the Internet: Why JavaScript and Node.js is right for Internet...
PPTX
Events for JavaScript event loop track.pptx
PPT
JavaScript Event Loop
KEY
A language for the Internet: Why JavaScript and Node.js is right for Internet...
KEY
Node.js
PPTX
A beginner's guide to eventloops in node
PDF
"You Don't Know NODE.JS" by Hengki Mardongan Sihombing (Urbanhire)
PDF
Node in Real Time - The Beginning
KEY
NodeJS
PPTX
Introduction to Node js
Playing With Fire - An Introduction to Node.js
"Node.js vs workers — A comparison of two JavaScript runtimes", James M Snell
Introduction to Node.js
Frontend Track NodeJS
Original slides from Ryan Dahl's NodeJs intro talk
NodeJS for Beginner
Node, can you even in CPU intensive operations?
Node.js Event Loop & EventEmitter
About Node.js
A language for the Internet: Why JavaScript and Node.js is right for Internet...
Events for JavaScript event loop track.pptx
JavaScript Event Loop
A language for the Internet: Why JavaScript and Node.js is right for Internet...
Node.js
A beginner's guide to eventloops in node
"You Don't Know NODE.JS" by Hengki Mardongan Sihombing (Urbanhire)
Node in Real Time - The Beginning
NodeJS
Introduction to Node js

More from TorontoNodeJS (6)

PDF
Safely Build, Publish & Maintain ES2015, ES2016 Packages Today
PDF
nlp_compromise
PDF
PDF
Avoiding callback hell with promises
PDF
Node as an API shim
PDF
Building your own slack bot on the AWS stack
Safely Build, Publish & Maintain ES2015, ES2016 Packages Today
nlp_compromise
Avoiding callback hell with promises
Node as an API shim
Building your own slack bot on the AWS stack

Recently uploaded (20)

PDF
Heart disease approach using modified random forest and particle swarm optimi...
PDF
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
PDF
A comparative analysis of optical character recognition models for extracting...
PDF
Profit Center Accounting in SAP S/4HANA, S4F28 Col11
PPTX
A Presentation on Artificial Intelligence
PDF
Agricultural_Statistics_at_a_Glance_2022_0.pdf
PDF
gpt5_lecture_notes_comprehensive_20250812015547.pdf
PPTX
Programs and apps: productivity, graphics, security and other tools
PDF
7 ChatGPT Prompts to Help You Define Your Ideal Customer Profile.pdf
PDF
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
PDF
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
PDF
Advanced methodologies resolving dimensionality complications for autism neur...
PDF
Video forgery: An extensive analysis of inter-and intra-frame manipulation al...
PPTX
OMC Textile Division Presentation 2021.pptx
PDF
Getting Started with Data Integration: FME Form 101
PDF
NewMind AI Weekly Chronicles - August'25-Week II
PDF
Mobile App Security Testing_ A Comprehensive Guide.pdf
PPTX
Machine Learning_overview_presentation.pptx
PPTX
Tartificialntelligence_presentation.pptx
PPTX
1. Introduction to Computer Programming.pptx
Heart disease approach using modified random forest and particle swarm optimi...
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
A comparative analysis of optical character recognition models for extracting...
Profit Center Accounting in SAP S/4HANA, S4F28 Col11
A Presentation on Artificial Intelligence
Agricultural_Statistics_at_a_Glance_2022_0.pdf
gpt5_lecture_notes_comprehensive_20250812015547.pdf
Programs and apps: productivity, graphics, security and other tools
7 ChatGPT Prompts to Help You Define Your Ideal Customer Profile.pdf
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
Advanced methodologies resolving dimensionality complications for autism neur...
Video forgery: An extensive analysis of inter-and intra-frame manipulation al...
OMC Textile Division Presentation 2021.pptx
Getting Started with Data Integration: FME Form 101
NewMind AI Weekly Chronicles - August'25-Week II
Mobile App Security Testing_ A Comprehensive Guide.pdf
Machine Learning_overview_presentation.pptx
Tartificialntelligence_presentation.pptx
1. Introduction to Computer Programming.pptx

Node.js API pitfalls

  • 1. Disclaimer ;-) Doesn't necessarily reflect the views of IBM, or any of my Node.js collaborators... 1
  • 2. nextTick •  [ ] in next tick of the event loop •  [ ] immediately after A returns 2 process.nextTick(function() { // Called ... });
  • 3. nextTick •  [ ] in next tick of the event loop •  [x] immediately after A returns 3 process.nextTick(function() { // Called ... });
  • 4. setImmediate •  [ ] in next tick of the event loop •  [ ] immediately after A returns 4 setImmediate(function() { // Called ... });
  • 5. setImmediate •  [x] in next tick of the event loop •  [ ] immediately after A returns 5 setImmediate(function() { // Called ... });
  • 6. nextTick vs setImmediate •  nextTick: adds callback to queue to be called immediately •  setImmediate: adds callback to queue to be called next tick 6
  • 7. worker.kill() 7 var cluster = require('cluster'); if (cluster.isMaster) { cluster.fork() .on('online', function() { this.kill('SIGHUP'); }) .on('exit', function() { console.log('Exit'); }); } else { process.on('SIGHUP', function() { console.log('Hup'); }); }
  • 8. worker.kill() •  [ ] "Hup" •  [ ] "Exit" •  [ ] "Hup" and "Exit" •  [ ] No output 8 var cluster = require('cluster'); if (cluster.isMaster) { cluster.fork() .on('online', function() { this.kill('SIGHUP'); }) .on('exit', function() { console.log('Exit'); }); } else { process.on('SIGHUP', function() { console.log('Hup'); }); }
  • 9. worker.kill() •  [ ] "Hup" •  [x] "Exit" •  [ ] "Hup" and "Exit" •  [ ] No output 9 var cluster = require('cluster'); if (cluster.isMaster) { cluster.fork() .on('online', function() { this.kill('SIGHUP'); }) .on('exit', function() { console.log('Exit'); }); } else { process.on('SIGHUP', function() { console.log('Hup'); }); }
  • 10. worker.kill() - what is it really? •  Use worker.process.kill() if you want to signal the worker •  Asymetric with worker.send(), worker.on('message', ...), etc. 10 Worker.prototype.kill = function(signo) { var proc = this.process; this.once('disconnect', function() { this.process.kill(signo || 'SIGTERM'); }); this.disconnect(); };
  • 11. worker.suicide - kill the worker •  [ ] true •  [ ] false 11 var cluster = require('cluster'); if (cluster.isMaster) { cluster.fork() .on('online', function() { this.kill('SIGTERM'); }) .on('exit', function() { // this.suicide is .... }); } else { }
  • 12. worker.suicide - kill the worker •  [x] true •  [ ] false 12 var cluster = require('cluster'); if (cluster.isMaster) { cluster.fork() .on('online', function() { this.kill('SIGTERM'); }) .on('exit', function() { // this.suicide is .... }); } else { }
  • 13. worker.suicide - exit the worker •  [ ] true •  [ ] false 13 var cluster = require('cluster'); if (cluster.isMaster) { cluster.fork() .on('exit', function() { // this.suicide is .... }); } else { process.exit(0); }
  • 14. worker.suicide - exit the worker •  [ ] true •  [x] false 14 var cluster = require('cluster'); if (cluster.isMaster) { cluster.fork() .on('exit', function() { // this.suicide is .... }); } else { process.exit(0); }
  • 15. worker.suicide - what does it mean? •  worker.disconnect() in parent (implicitly done by worker.kill()) •  process.disconnect() in worker 15 Actual meaning: "orderly exit"
  • 16. url.parse/format 16 var url = require('url'); var uri = url.parse('http://example.com:8080/path'); uri.host = 'ibm.com'; console.log(url.format(uri)); •  [ ] yes •  [ ] no Prints http://ibm.com:8080/path ...
  • 17. url.parse/format 17 var url = require('url'); var uri = url.parse('http://example.com:8080/path'); uri.host = 'ibm.com'; console.log(url.format(uri)); •  [ ] yes •  [x] no: http://ibm.com/path Prints http://ibm.com:8080/path ...
  • 18. url.parse/format •  host is "example.com:8080" •  hostname is "example.com" 18
  • 19. url.parse/format 19 var url = require('url'); var uri = url.parse('http://example.com:8080/path'); uri.hostname = 'ibm.com'; console.log(url.format(uri)); •  [ ] yes •  [ ] no Prints http://ibm.com:8080/path ...
  • 20. url.parse/format 20 var url = require('url'); var uri = url.parse('http://example.com:8080/path'); uri.hostname = 'ibm.com'; console.log(url.format(uri)); •  [ ] yes •  [x] no: http://example.com:8080/path Prints http://ibm.com:8080/path ...
  • 21. url.parse/format •  hostname and port are ignored if host is present... 21
  • 22. url.parse/format 22 var url = require('url'); var uri = url.parse('http://example.com:8080/path'); delete uri.host; uri.hostname = 'ibm.com'; console.log(url.format(uri)); •  http://ibm.com:8080/path, finally!
  • 23. url.parse/format •  parse: "http://example:8080": – port is 8080 – host is "example:8080"... why not hostport? – hostname is "example"... why not host? •  format: hostname and port are ignored if hostname is present – shouldn't they be prefered if present? 23
  • 24. path.parse/format 24 var path = require('path'); var bits = path.parse('some/dir/index.txt'); // has: .name, .ext bits.ext = '.html'; console.log(path.format(bits)); Logs some/dir/index.html... •  [ ] yes •  [ ] no
  • 25. path.parse/format 25 var path = require('path'); var bits = path.parse('some/dir/index.txt'); bits.ext = '.html'; console.log(path.format(bits)); Logs some/dir/index.html... •  [ ] yes •  [x] no: some/dir/index.txt
  • 26. path.parse/format 26 var path = require('path'); var bits = path.parse('some/dir/index.txt'); console.log(bits.base); // > index.txt delete bits.base bits.ext = '.html'; console.log(path.format(bits)); Logs some/dir/index.html... •  [ ] yes •  [ ] no Its probably like url...
  • 27. path.parse/format 27 var path = require('path'); var bits = path.parse('some/dir/index.txt'); console.log(bits.base); // > index.txt delete bits.base bits.ext = '.html'; console.log(path.format(bits)); Logs some/dir/index.html... •  [ ] yes •  [x] no: some/dir/
  • 28. path.parse/format 28 var path = require('path'); var bits = path.parse('some/dir/index.txt'); bits.base = bits.name + '.html'; console.log(path.format(bits)); // > some/dir/index.html Its just completely different, format always ignores name and ext. Correct:
  • 29. streams v1, 2, 3 29 Note that, for backwards compatibility reasons, removing 'data' event handlers will not automatically pause the stream. Also, if there are piped destinations, then calling pause() will not guarantee that the stream will remain paused once those destinations drain and ask for more data. From https://nodejs.org/api/stream.html#stream_class_stream_readable:
  • 30. streams v1, 2, 3 30 Note that, for backwards compatibility reasons, removing 'data' event handlers will not automatically pause the stream. Also, if there are piped destinations, then calling pause() will not guarantee that the stream will remain paused once those destinations drain and ask for more data. From https://nodejs.org/api/stream.html#stream_class_stream_readable: When can we delete backwards compat to v0.8?
  • 31. Question •  Laurie Voss (@seldo), NPM, Nov. 28, 2015 31 What kind of node do you want: a good one? or a (mostly) v0.8 compatible one? @octetcloud As of right now, 30.4% of registered npm accounts are less than 6 months old.
  • 32. Answer •  github, twitter, etc.. •  Be tolerant (or intolerant) of breakages, but what you say will effect what happens. 32 Its yours (and a lot of other people's) Node.js, speak out:
  • 33. Flames to: •  github: @sam-github •  email: rsam@ca.ibm.com •  twitter: @octetcloud •  talk source: https://gist.github.com/sam-github/4c5c019b92cf95fb6571, or https://goo.gl/2RWpqE 33