- JavaScript
- Table of Contents
- ESLint and Prettier
- JavaScript Engine
- JavaScript Runtime
- Hoisting
- IIFE
- This
- apply() and call()
- Function currying - bind()
- Recap
this
- Types
- Invoking functions
- Higher-Order Functions
- Prototypal Inheritance
- FP and OOP
- Constructor Function
- Promises
- Queue Priority
- Concurrency and Parallelism
- Modules
- Design Patterns
- References
- Add eslint and prettier plugins for Visual Studio Code
- Press
command
+shift
+p
and search 'settings.json'. See vscode-settings.json
The settings include vue.js (You need to install vuter
as well) and react-native formatting.
So, is JavaScript an interpreted language? Ans: Not technically. Because it depends on how the JS engine deals with it.
JS engine cannot optimise code snippets properly with these keywords. I.e. These keywords are functional, but it would slow the execution a little bit.
- eval()
- arguments
- for in
- with
- delete
- hidden classes
- inline caching
In the JavaScript engine, we need memory heap and call stacks to store data and execute code.
A memory heap is a place that holds data as a cabinet, which allocates and releases memory. It allows us to point to memory with variables, storing any arbitrary data. For example, the following code const num = 1;
instructs the JavaScript engine to allocate memory for a number.
Call stacks keep track of where we are in the code to run the code in order. Every time we invoke functions, we use call stacks. As we run the code, it pushes functions into the stack and pup out after the execution.
Here are steps of how JavaScript engine executes the function as follows:
function func2() {
return true;
}
function func1() {
return func2();
}
func1();
- JavaScript engine creates an execution context for
func1()
. - JavaScript engine adds that execution context to the stack.
- The JavaScript engine creates another execution context for
func2()
and adds it to the stack. - After the
func2()
has been executed, the JavaScript engine will remove its execution context from the stack as well asfunc1()
.
The very first execution context that the JavaScript engine creates is the global execution context, which generates global objects and this
in the real world.
JavaScript is a single-threaded programming language. But sometimes JavaScript can be ''asynchronous, and that is because browsers provide Web APIs, which is written in languages like C++ to help JavaScript deal with asynchronous tasks. E.g. DOM, fetch.
Being single-threaded means there is only one call stack while running. Take a look at an example of call stacks:
function a() {
b();
}
function b() {
console.log('done!');
}
a();
The call stack is:
a();
b();
a();
console.log('done!');
b();
a();
Once all the functions have been executed, they pop up in order.
b();
a();
a();
// empty
To see how exactly call stack and Web APIs work, go to loupe.
As you might have heard, Node.js makes JavaScript be able to run outside of the browser. This program uses a V8 engine to interpret JavaScript, creating the entire environment to run JavaScript code and offer additional APIs such as the global object.
On top of the engine, a browser has Web APIs
. It offers things like DOM
, web APIs, e.g., Timeout(setTimeout)
.
JavaScript engine allocates memory for variables and functions during the creation phase. E.g.,
console.log(teddy);
console.log(sing());
var teddy = 'bear';
function sing() {
console.log('Hello from the other side...');
}
// undefined
// 'Hello from the other side...'
The above code snippet is not going to crash our apps because the JavaScript engine hoists it before running console.log(teddy);
, it executes var teddy = undefined;
, called half hoisting Functions. JavaScript engine gives variables declared with the var
keyword an undefined
value before actually running. On the other hand, functions are fully hoisting. They are moved to the top of the code, and therefore, we can invoke functions before they are declared.
Notice, hoisting works with the var
and function
keywords. const
and let
are not hoisted. A follow-up question is, what if we create a function using function expression? Is it half hosting or fully hoisting?
console.log(teddy);
console.log(snoopy);
sing();
let teddy = 'bear';
const snoopy = 'dogs';
// function expression
var sing = function() {
console.log('Hello from the other side...');
}
// Uncaught ReferenceError: Cannot access 'teddy' before initialization
// Uncaught ReferenceError: Cannot access 'snoopy' before initialization
// Uncaught TypeError: sing is not a function (Because 'sing' is undefined)
console.log('#1, a=', a);
var a = 10;
console.log('#2, a=', a);
var a = 20;
console.log('#3, a=', a);
You get: #1 = undefined, #2 = 10, #3 = 20
console.log('#1, f()=', f());
function f() {
return 0;
}
console.log('#2, f()=', f());
function f() {
return 1;
}
console.log('#3, f()=', f());
You get: #1 = 1, #2 = 1, #3 = 1
var favouriteFood = 'hot dog';
function toGo() {
console.log(`I\'d like some ${favouriteFood}`)
var favouriteFood = 'pizza';
console.log(`A ${favouriteFood} is just fine`);
}
toGo();
The result is:
'I'd like some undefined.'
'A pizza is just fine'
With JavaScript hoisting, this code is executed as:
/*----------------------------------------*/
/**************** Hoisting ****************/
/*----------------------------------------*/
// Assign undefined to statements start with 'var'
var favouriteFood = undefined;
// Define statements start with 'function'
function toGo() {
console.log(`I\'d like some ${favouriteFood}`)
var favouriteFood = 'pizza';
console.log(`A ${favouriteFood} is just fine`);
}
/*----------------------------------------*/
/**************** Hoisting ****************/
/*----------------------------------------*/
var favouriteFood = 'hot dog';
toGo();
IIFE is known as Immediately-invoked Function Expression, a typical JavaScript design pattern.
It lets you call functions immediately right after it is created. E.g.
Typically, you cannot code as follows:
function a() {
// do something
}();
// error -> Uncaught SyntaxError: Unexpected token ')'
With IIFE, you can:
// style 1
(function a() {
console.log('a is running...');
}
)()
// a is running...
// style 2
(function a() {
console.log('a is running...');
}()
)
// a is running...
Eventually, you can have one global variable containing objects and functions. The advantage is that this code snippet only pollutes the global execution context once, i.e. script1
. In the following examples, you are scoping things into their environments.
let script1 = (function () {
function a1() {
return 5;
}
return {
a1: a1
}
})()
script1.a1(); // 5
You can access arguments if you need
let script2 = (function (num) {
function a1() {
return num;
}
return {
a1: a1
}
})(12345)
script2.a1(); // 12345
Example 1,
function a() {
console.log('a', this);
function b() {
console.log('b', this);
const c = {
print: function() {
console.log('c', this);
}
}
c.print();
}
b();
}
a();
If you execute the above piece of code on the browser, you will get:
- a > the Window object
- b > the Window object
- c > {print: f}
It does not matter where we write a piece of code. All that matters is how they get called during invocation.
Example 2,
const obj = {
f1() {
console.log('f1', this);
function f2() {
console.log('f2', this);
}
f2();
},
};
obj.f1();
- f1 > {f1: f}
- f2 > the Window object
Again, it does not matter where the code is written. All that matters is where the code is executed. f1
is called inside the obj
, and f2
is called inside f1
.
In JavaScript, our lexical scope (available data + variables where the function was defined) determines our available variables, not where the function is called (dynamic scope). Everything in JavaScript is lexically scoped EXCEPT FOR THE this
KEYWORD.
To fix such an issue, we can either use arrow functions from ES6 or binding objects. We know that arrow functions are lexically bound.
Now, we can change the above example so that the this
can be bound to f1
in f2
. This approach is more recommended than the next one.
const obj = {
f1() {
console.log('f1', this);
const f2 = () => {
console.log('f2', this);
};
f2();
},
};
obj.f1();
- f1 > {f1: f}
- f2 > {f1: f}
Another method to address such an issue is to bind the this
to f1
. This approach can be written in two ways.
const obj = {
f1() {
console.log('f1', this);
function f2() {
console.log('f2', this);
}
return f2.bind(this);
},
};
obj.f1()();
const obj = {
f1() {
console.log('f1', this);
const self = this;
function f2() {
console.log('f2', self);
}
f2();
},
};
obj.f1();
- f1 > {f1: f}
- f2 > {f1: f}
var name = 'Alice';
function whoAmI() {
console.log(this.name);
}
const obj = {
name: 'Bob',
whoAmI: function() {
console.log(this.name);
}
}
whoAmI(); // Alice
obj.whoAmI(); // Bob
Let's change it a little bit.
var name = 'Alice';
function whoAmI() {
console.log(this.name);
}
const obj = {
name: 'Bob',
whoAmI: function() {
var f = function() {
console.log(this.name);
};
return f();
}
}
whoAmI(); // Alice
obj.whoAmI(); // Alice
To make obj.whoAmI()
print Bob
, here are three solutions:
- ES6 arrow function
Arrow functions are lexically scoped, whereas functions are dynamically scoped.
Example 1: Regular function vs arrow function
var skill = 'fists of thunder';
function DemonHunter() {
this.job = 'demon hunter';
this.skill = 'cluster arrow';
}
const eve = new DemonHunter();
// regular function
DemonHunter.prototype.attack = function() {
console.log(this.skill);
}
eve.attack(); // cluster arrow
// arrow function
DemonHunter.prototype.attack = () => {
console.log(this.skill);
}
eve.attack(); // fists of thunder
Example 2: Put an arrow function inside a regular function
const obj = {
name: 'Bob',
whoAmI: function () {
var f = () => {
console.log(this.name);
};
return f();
},
};
obj.whoAmI(); // Bob
- Using the
this
keyword to bind an object.
const obj = {
name: 'Bob',
whoAmI: function() {
var f = function() {
console.log(this.name);
};
return f.bind(this)();
}
}
obj.whoAmI(); // Bob
- Creating a variable and pointing to the object.
const obj = {
name: 'Bob',
whoAmI: function() {
var self = this;
var f = function() {
console.log(self.name);
};
return f();
}
}
obj.whoAmI(); // Bob
More examples of how the this
keyword works in JavaScriptreact
var name = 'unknown';
const a = {
name: 'a',
echo() {
return this.name;
}
}
const b = {
name: 'b',
echo() {
return function() {
return this.name;
}
}
}
const c = {
name: 'c',
echo() {
return () => {
return this.name;
}
}
}
a.echo(); // a
b.echo()(); // unknown
c.echo()(); // c
Now, this one is tricky.
var name = 'unknown';
const d = {
name: 'd',
echo() {
return this.name;
}
}
d.echo(); // d
const trick = d.echo;
trick(); // unknown
To solve this issue, you have to bind trick
to the lexical scope, i.e. link this
to d
object.
const trick = d.echo.bind(d);
trick(); // d
We first go through the code snippet.
let wizard = {
name: 'Merlin',
health: 80,
heal() {
return this.health = 100;
}
}
console.log(wizard); // {name: "Merlin", health: 80, heal: ƒ}
wizard.heal();
console.log(wizard); // {name: "Merlin", health: 100, heal: ƒ}
Then you have another object. This Robin can't heal
himself.
let archer = {
name: 'Robin Hood',
health: 30
}
To heal
someone who does not know how to use magic, i.e. To borrow a function from another object, you can use apply()
or call()
.
// {name: "Robin Hood", health: 30}
wizard.heal.call(archer);
// {name: "Robin Hood", health: 100}
The only difference between apply()
and call()
is the way to access arguments.
let wizard = {
name: 'Merlin',
health: 80,
heal() {
return this.health = 100;
},
fly(d1, d2, d3) {
this.health -= d1;
this.health -= d2;
this.health -= d3;
return this.health;
}
}
let archer = {
name: 'Robin Hood',
health: 30
}
wizard.fly.call(archer, 1, 3, 5);
// {name: "Robin Hood", health: 21}
Or
wizard.fly.apply(archer, [1, 3, 5]);
// {name: "Robin Hood", health: 21}
Another example: To find out the max number in a given array
function getMaxNumber(arr){
return Math.max.apply(null, arr);
}
function add(a, b) {
return (a + b);
}
add(1, 1); // 2
Bind the add()
and see what will happen
const addTwo = add.bind(this, 2);
addTwo(1); // 3
const addTen = add.bind(this, 10);
addTen(1); // 11
- Implicit binding
const person = {
name: 'Karen',
showName() {
console.log(this.name);
}
}
person.showName(); // Karen
- Explicit binding
E.g. To bind window
to this.
const person = {
name: 'Karen',
browsingHistory: function() {
console.log(this.history.length);
}.bind(window)
}
person.browsingHistory(); // 1
- Arrow function
const person = {
name: 'Karen',
showName: function() {
let sayMyName = () => {
// without arrow function, `this` should be `window`
console.log(this.name);
}
return sayMyName();
}
}
JavaScript has seven types: number, string, boolean, undefined, null, object, symbol. The symbol is a new type being added to ES6. undefined
is an absence of a definition, whereas null
is an absence of value. Underneath the hook, functions and arrays are objects. All the types above except objects are primitives. The value of those types matches the exact value stored in memory; there is no ambiguity. An object, on the other hand, contains references. It refers to somewhere in memory, not a value.
function f1() {
// do something
}
const obj = {
f2: function () {
// do something
},
// ES6 syntax
f3() {
// do something
},
}
const f4 = function () {
// do something
};
f1();
obj.f2();
obj.f3()
f4();
Function returns function.
E.g. A HOF function
const multiplyBy = function(n1) {
return function(n2) {
return n1 * n2;
}
}
Or it can be written in ES6 style.
const multiplyBy = (n1) => (n2) => n1 * n2;
And you can call the function.
const multiplyByTen = multiplyBy(10);
multiplyByTen(2); // 20
multiplyByTen(5); // 50
Closures are also called lexical scope. Suppose we want to keep a variable inside of a function. A HOF can help with it.
const foo = (a) => (b) => (c) => console.log(a, b, c);
let fa = foo('a');
// executed lots of time-consuming tasks...
fa('b')('c'); // a, b, c
What encapsulation does is hide some information from the outside world.
For example:
const makeNuclearButton = () => {
let timeWithoutDestruction = 0;
const passTime = () => timeWithoutDestruction++; // hide this method
const totalPeaceTime = () => timeWithoutDestruction;
const launch = () => {
timeWithoutDestruction = -1;
return '💥';
}
setInterval(passTime, 1000);
return {
launch,
totalPeaceTime
}
}
const ohNo = makeNuclearButton();
ohNo.totalPeaceTime(); // 5
ohNo.launch(); // 💥
In JavaScript, there are no classes. Instead, we have what is called prototypal inheritance. Suppose we want lizardmen
to invoke the sunbathe from the lizard
object. The code is shown below.
const lizard = {
name: 'lizard',
sunbathe: () => {
console.log('sunbathing...')
}
}
const lizardmen = {
name: 'lizardman',
}
The best solution is to bind the sunbathe
function the lizardmen
object.
const lizardmenSunbathe = lizard.sunbathe.bind(lizardmen);
lizardmenSunbathe();
By using __proto__
, we can inherit the sunbathe
function. However, we should never do it this way. It is possible what we mess up new properties with default properties.
lizardmen.__proto__ = lizard;
lizardmen.sunbathe();
Lastly, we can create a prototype chain up to the lizard
object.
const lizardmen = Object.create(lizard);
lizardmen.name = 'lizardmen';
lizardmen.sunbathe();
Another concept is prototype
.
__proto__
points toprototype
.- We use
prototype
when we have a constructor function. For example,
function Necromancer(name, weapon, skill) {
this.name = name;
this.weapon = weapon;
this.skill = skill;
this.job = 'necromancer';
}
Necromancer.prototype.attack = function () {
console.log(this.skill);
}
const necromancer1 = new Necromancer('Simon', 'corpse lance', 'devour');
necromancer1.attack(); // devour
necromancer1.__proto__.attack; // function () { console.log(this.skill); }
Necromancer.prototype.attack // function () { console.log(this.skill); }
necromancer1.prototype; // undefined -> Only functions have access to prototype
Necromancer.prototype === necromancer1.__proto__; // true
Example 2
const lizard = {
name: 'lizard',
isVenomous: true
}
const lizardman = {
name: 'lizardman',
canDream: true
}
lizardman.__proto__ = lizard;
lizardman.canDream; // true
lizardman.isVenomous; // true
lizardman // {name: "lizardman" canDream: true __proto__: {name: "lizard" isVenomous: true __proto__: {...}}
lizardman.hasOwnProperty('canDream'); // true
lizardman.hasOwnProperty('isVenomous'); // false
E.g. Create a format()
of Date
Date.prototype.format = function() {
let formattedDate = '';
const yyyy = this.getFullYear();
const mm = this.getMonth() + 1;
const dd = this.getDate();
formattedDate += (mm > 9) ? mm: `0${mm}`;
formattedDate += '/';
formattedDate += (dd > 9) ? dd : `0${dd}`;
formattedDate += '/';
formattedDate += yyyy;
return formattedDate;
}
new Date().format(); // 02/20/2020
FP, aka function programming
OOP, aka object-oriented programming
An OOP example:
const necromancer1 = {
name: 'Simon',
job: 'necromancer',
weapon: 'corpse lance',
skill: 'devour',
attack() {
console.log(skill);
}
}
const necromancer2 = {
name: 'Lucas',
job: 'necromancer',
weapon: 'cauldron',
skill: 'frailty',
attack() {
console.log(skill);
}
}
In FP, it is
function createNecromancer(name, weapon, skill) {
return {
name,
weapon,
skill,
job: 'necromancer'
}
}
const necromancerFunctions = {
attack() {
console.log(this.skill);
}
}
const necromancer1 = createNecromancer('Simon', 'corpse lance', 'devour');
necromancer1.attack = necromancerFunctions.attack;
const necromancer2 = createNecromancer('Lucas', 'cauldron', 'frailty');
necromancer2.attack = necromancerFunctions.attack;
Instead of attaching attack
manually (it will be a nightmare if you have hundreds of necromancers). You can use Object.create
to add the attack function at the beginning.
const necromancerFunctions = {
attack() {
console.log(this.skill);
}
}
function createNecromancer(name, weapon, skill) {
let necromancer = Object.create(necromancerFunctions);
necromancer.name = name;
necromancer.weapon = weapon;
necromancer.skill = skill;
necromancer.job = 'necromancer';
return necromancer;
}
const necromancer1 = createNecromancer('Simon', 'corpse lance', 'devour');
const necromancer2 = createNecromancer('Lucas', 'cauldron', 'frailty');
Instead of using Object.create()
, you can use constructor functions.
With the new
keyword, you are creating a new object.
Two rules to implement a constructor function:
- add
new
. - function name starts with a capital letter. (coding style)
function Necromancer(name, weapon, skill) {
this.name = name;
this.weapon = weapon;
this.skill = skill;
this.job = 'necromancer';
}
Necromancer.prototype.attack = function () {
console.log(this.skill);
}
const necromancer1 = new Necromancer('Simon', 'corpse lance', 'devour');
const necromancer2 = new Necromancer('Lucas', 'cauldron', 'frailty');
In the proceeding code snippet, this
of Necromancer refers to necromancer1
and necromancer2
because of new
.
A promise is an object that may produce a single value sometime in the future. Either a resolved value or a reason that it's not resolved (rejected)
E.g.
const promise = new Promise((resolve, reject) => {
if (true) {
resolve('stuff worked');
} else {
reject('error, it broke');
}
});
promise.then(result => console.log(result)); // stuff worked
promise.then(result => `result: ${result}`)
.then(result => `${result}?`)
.catch(err => console.log(err))
.then(result => console.log(`${result}!`)); // result: stuff worked?!
Because the catch statement
is before then
, which means if any error happens in the last then
code snippet, this catch
cannot handle it.
The Promise.all()
waits until all the promises are resolved and then log out the values.
const p1 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, '100ms');
})
const p2 = new Promise((resolve, reject) => {
setTimeout(resolve, 1000, '1s');
})
const p3 = new Promise((resolve, reject) => {
setTimeout(resolve, 3000, '3s');
})
Promise.all([p1, p2, p3]).then(value => {
console.log(value);
// After 3 seconds, you get an array -- ["100ms", "1s", "3s"]
})
More examples
const URLs = [
'https://jsonplaceholder.typicode.com/posts',
'https://jsonplaceholder.typicode.com/comments',
'https://jsonplaceholder.typicode.com/albums'
];
Promise.all(URLs.map(url => {
return fetch(url).then(response => response.json());
})).then(results => {
console.log("Posts:", results[0]);
console.log("Comments:", results[1]);
console.log("Albums:", results[2]);
});
If any of the URLs are wrong, you get failed responses.
const URLs = [
'https://jsonplaceholder.typicode.com/posts',
'https://jsonplaceholder.typicode.com/comments',
'???'
];
Promise.all(URLs.map(url => {
return fetch(url).then(response => response.json());
})).then(results => {
console.log("Posts:", results[0]);
console.log("Comments:", results[1]);
console.log("Albums:", results[2]);
}).catch(() => console.log('Failed to fetch data.'));
You pick up the fastest resolve and don't care about the others.
const p1 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, '100ms');
})
const p2 = new Promise((resolve, reject) => {
setTimeout(resolve, 1000, '1s');
})
const p3 = new Promise((resolve, reject) => {
setTimeout(resolve, 3000, '3s');
})
Promise.race([p1, p2, p3]).then(value => {
console.log(value);
// After 100ms, you gets "100ms"
})
For example
const promisify = (item, delay) => new Promise((resolve, reject) => {
setTimeout(() => resolve(item), delay);
});
const p1 = promisify('a', 1000);
const p2 = promisify('b', 3000);
const p3 = promisify('c', 2000);
- Execute three promises in parallel
async function parallel() {
const promises = [p1, p2, p3];
const [output1, output2, output3] = await Promise.all(promises);
return `Parallel is done: ${output1} ${output2} ${output3}`
}
parallel().then(console.log); // Parallel is done: a b c
- Get the first result by leveraging
Promise.race
(In this case, you don't care about all the other ones)
async function race() {
const promises = [p1, p2, p3];
const output = await Promise.race(promises);
return `Race is done: ${output}`
}
race().then(console.log); // Race is done: a
- Sequence
async function sequence() {
const output1 = await p1;
const output2 = await p2;
const output3 = await p3;
return `Sequence is done: ${output1} ${output2} ${output3}`
}
sequence().then(console.log); // Sequence is done: a b c
In JavaScript, there are three queues:
- Callback Queue (Task Queue)
setTimeout(() => {
console.log('hello from setTimeout');
}, 0);
- Job Queue (Microtask Queue)
Promise.resolve('hello from promise')
.then(data => console.log(data));
- Others
console.log('hello from console.log')
Now, if you run those queues at a time, you get:
setTimeout(() => {
console.log('hello from setTimeout');
}, 0);
Promise.resolve('hello from promise')
.then(data => console.log(data));
console.log('hello from console.log');
// hello from console.log
// hello from promise
// hello from setTimeout
Event loop checks Job queue first. Make sure it's empty before it starts the callback queue.
- Concurrency: single-core CPU, do task one by on
- Concurrency + Parallelism: Multi-core CPU, do tasks concurrently.
ES6, ECMAScript 2015
- separate an array
Let's say you have a function with four arguments available.
function sum (a, b, c, d) {
return a + b + c + d;
}
You can stuff arguments from an array by retrieving each one.
const arr = [1, 2, 3, 4];
sum(...arr); // 10
ES8, ECMAScript 2017
async
/await
syntax
E.g., fetch
in ES6 style.
const callback = (res) => console.log('res', res);
function getComments(callback) {
return fetch('https://jsonplaceholder.typicode.com/posts/1/comments')
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
return response.json();
})
.then((json) => {
return callback(json);
})
.catch(console.error);
}
getComments(callback);
fetch
in ES8 style.
const getComments = async () => {
try {
const response = await fetch(
'https://jsonplaceholder.typicode.com/posts/1/comments'
);
if (!response.ok) {
throw Error(response.statusText);
}
const json = await response.json();
return json;
} catch (error) {
console.error(error);
}
};
getComments().then((res) => console.log('res', res));
Example 2, Promise.all
in ES6 style
const URLs = [
'https://jsonplaceholder.typicode.com/posts',
'https://jsonplaceholder.typicode.com/comments',
'https://jsonplaceholder.typicode.com/albums'
];
Promise.all(URLs.map(url => {
return fetch(url).then(response => response.json());
})).then(results => {
console.log("Posts:", results[0]);
console.log("Comments:", results[1]);
console.log("Albums:", results[2]);
}).catch(() => console.log('Failed to fetch data.'));
Promise.all
in ES8 style
const URLs = [
'https://jsonplaceholder.typicode.com/posts',
'https://jsonplaceholder.typicode.com/comments',
'https://jsonplaceholder.typicode.com/albums'
];
try {
const [posts, comments, albums] = await Promise.all(URLs.map(url => {
return fetch(url).then(response => response.json());
}));
console.log("Posts:", posts);
console.log("Comments:", comments);
console.log("Albums:", albums);
} catch (err) {
console.log('Failed to fetch data.');
}
ES9, ECMAScript 2018
- Object spread operator
Let's say you have an object myGarage
const myGarage = {
sedan: 'Tesla',
suv: 'Land Rover',
sportsCar: 'Lamborghini'
}
With an object spread operator, you can separate the object effortlessly.
const { sedan, ...rest } = myGarage;
console.log(sedan); // 'Tesla'
console.log(rest); // {suv: "Land Rover", sportsCar: "Lamborghini"}
- For await of
In ES8, you can have Promise.all
like this:
const URLs = [
'https://jsonplaceholder.typicode.com/posts',
'https://jsonplaceholder.typicode.com/comments',
'https://jsonplaceholder.typicode.com/albums'
];
try {
const [posts, comments, albums] = await Promise.all(URLs.map(url => {
return fetch(url).then(response => response.json());
}));
console.log("Posts:", posts);
console.log("Comments:", comments);
console.log("Albums:", albums);
} catch (err) {
console.log('Failed to fetch data.');
}
Here is what you can do in ES9,
const URLs = [
'https://jsonplaceholder.typicode.com/posts',
'https://jsonplaceholder.typicode.com/comments',
'https://jsonplaceholder.typicode.com/albums'
];
const arrayOfPromises = URLs.map(url => fetch(url));
const responses = [];
for await (let request of arrayOfPromises) {
const response = await request.json();
responses.push(response);
}
console.log("Posts:", responses[0]);
console.log("Comments:", responses[1]);
console.log("Albums:", responses[2]);
Check out my Design-Patterns repo.