- Trailing Commas
- Object.values / Object.entries
- Object.getOwnPropertyDescriptors
- String Padding
- async / await
Trailing commas
allow us to add a comma to the last parameter on a function declaration and execution.
This is specially useful when declaring or calling a function by placing each parameter per line. Great for version control.
// Function Declaration
function foo(
a,
b,
c, // <- comma
) {}
// Function Execution
foo(
1,
2,
3, // <- comma
)
Object.values
returns all values of an object in the same order as the for...in
loop.
var obj = {
foo: 'Mr.Foo',
bar: 'Mr.Bar'
}
Object.values(obj) // [ 'Mr.Foo', 'Mr.Bar' ]
Object.entries
returns a pair [key , value] for each element in the object.
Object.entries(obj) // [ [ 'foo', 'Mr.Foo' ], [ 'bar', 'Mr.Bar' ] ]
Object.getOwnPropertyDescriptors
returns all object properties descriptors.
console.log(Object.getOwnPropertyDescriptors(obj))
// { foo:
// { value: 'Mr.Foo',
// writable: true,
// enumerable: true,
// configurable: true },
// bar:
// { value: 'Mr.Bar',
// writable: true,
// enumerable: true,
// configurable: true } }
String padding
pads the current string with the provided string until it reaches the specified length.
// String.prototype.padStart
'foo'.padStart(10) // ' foo'
'foo'.padStart(10,'123') // '1231231foo'
'foo'.padStart(10, undefined) // ' foo'
// String.prototype.padEnd
'foo'.padEnd(10) // 'foo '
'foo'.padEnd(10,'123') // 'foo1231231'
'foo'.padEnd(10, undefined) // 'foo '
async / await
is the new Javascript syntax to declare an asynchronous function
.
An async function
returns an AsyncFunction
object.
> Object.getPrototypeOf(async function (){})
AsyncFunction {}
Before exploring async / await
let's first go back to Promises
and move step-by-step.
Javascript Promises
already allowed us to do asynchronous development.
By creating a Promise
we are representing a value that will be available now, or in the future, or never.
A Promise
state can be:
- pending - meaning that the
Promise
was neither resolved nor rejected. Represents aPromise
initial state. - resolved - meaning that the operation, wrapped by the
Promise
, completed successfully. - rejected - meaning that the operation failed.
var getRandomWithPromise = (error) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (error) {
// When an error occurs we reject our promise.
reject('some error'); return;
}
// We resolve our promise with a random number.
resolve(Math.floor(Math.random() * 100));
}, 200);
});
}
Here's an example of how we can use our getRandomWithPromise
.
getRandomWithPromise()
// The resolver function to run when the promise is resolved.
.then((result) => {
console.log(`Your random number is ${result}!`);
})
// The resolver function to run when the promise is rejected.
.catch((error) => {
console.log(`Ups! Something went wrong! Details: ${error}`);
});
Generators
allows us to stop a function execution until something happens.
When building a generator
we are able to control when a function execution should pause and recover.
Next we will build a generator
that will allows us to do some asynchronous development.
var getRandomWithGenerator = (generator, error) => {
// We first instantiate the generator.
var g = generator();
// And move forward to stop on the first yield field.
g.next();
// This setTimeout simulates that something asynchronous is happening (e.g. an HTTP request).
// Although our code is able to proceed, our generator is suspended.
setTimeout(function () {
// At this point our asynchronous action is done and we should be able to do something with the response.
// If an error occurs we can throw it through the generator.
if (error) {
g.throw('some error'); return;
}
// If all is good, we want to play our generator by calling the .next() method with the response.
g.next(Math.floor(Math.random() * 100));
}, 200);
}
An example of use to our getRandomWithGenerator
could be the following.
getRandomWithGenerator(
// This function represents the operations that we want to run as soon as our generator 'fulfills'.
function* onFulfill() {
try {
// yield will have the generator's result.
var result = yield;
console.log(`Your random number is ${result}!`);
} catch(error) {
// Because the generator can throw we should wrap our code into a try / catch block.
console.log(`Ups! Something went wrong! Details: ${error}`);
}
}
);
Using generators
for asynchronous development makes our job really hard.
Also, the code becomes a bit unreadable.
We should use promises or the new async / await
for this.
async / await
is finally here and we can use it to make our asynchronous lives easier 🤘
async
is the keyword that specifies that a function is asynchronous
.
await
is the operator used to wait for a promise to be fulfilled.
It can only be used inside an async function
.
async function () {
// await
}
Let's build an example following the same idea of getting a random number.
const getRandomWithAsync = async () => {
try {
return await getRandomWithPromise();
} catch(error) {
return error;
}
}
The first thing to notice is the keyword async
which reveals our arrow function as an asynchronous function.
Our await
operator takes the promise getRandomWithPromise
and pauses the function execution until the promise has been fulfilled.
When fulfilled the promise can be:
- resolved - meaning that
await
will return the resolved value. - rejected - meaning that
await
will thrown the rejected value.
Because a promise can thrown an error it is important to wrap our code with a try / catch
block.
Please note that the body of the function reads as an almost synchronous function. There's no longer the need for indentation as with promise chain.
Here's an example of use.
// An async function returns a promise, therefore it can be used with promise chain.
getRandomWithAsync()
.then((result) => {
console.log(`Your random number is ${result}!`);
})
.catch((error) => {
console.log(`Ups! Something went wrong! Details: ${error}`);
});
Unfortunately await
can only be used inside an async function
.
var random = await getRandomWithAsync(); // Syntax Error.
In order to use await
as described in the line above we need to wrap it around an async IIFE
.
(async function () {
console.log(`Your random number is ${await getRandomWithAsync()}!`);
})();
We can also create async functions
inside classes
.
class Random {
async getRandom () {
return await getRandomWithPromise();
}
}
(async function () {
var random = new Random();
console.log(`Your random number is ${await random.getRandom()}!`)
})();
It will happen the case where we need to wait for multiple promises to fulfill.
We can do it in two ways - sequentially
and concurrently
.
(async function () {
// Wait for the first promise to be fulfilled.
var a = await getRandomWithPromise();
// Wait for the second promise to be fulfilled.
var b = await getRandomWithPromise();
console.log(`Your random numbers are ${a} and ${b}!`);
})();
// [Execution Time] 0.490 ms total
This handling is complete sequential.
Our promise b
is only executed after our promise a
fulfills.
This is a major performance issue because we could have run both promises concurrently and take less time.
(async function () {
// Request the random numbers and save the promises.
var aPromise = getRandomWithPromise();
var bPromise = getRandomWithPromise();
// At this point, the requests were both executed concurrently, meaning
// that we didn't wait for the first to finish to request for the second.
// We now just need to wait for both promises to be fulfill.
var a = await aPromise;
var b = await bPromise;
// The function execution time would be equal to the promise that takes the most time.
console.log(`Your random numbers are ${a} and ${b}!`);
})();
// [Execution Time] 0.283 ms total
One of the advantages with Promise.all
is that it has the fail-fast behaviour.
If a promise fails, Promise.all
will not wait for the other promises to be fulfill. It rejects immediately.
(async function () {
var [a,b] = await Promise.all([
getRandomWithPromise(),
getRandomWithPromise()
]);
console.log(`Your random numbers are ${a} and ${b}!`);
})();
The use of the await
operator is not restricted to promises.
(async function () {
var random = await 3;
// await will convert any non-promise value into a promise value, meaning that
// await will wrap the followed expression into 'Promise.resolve'.
console.log(random); // 3
})();
await
can be used with any object with a .then()
method (i.e. a thenable).
var thenable = {
then(resolve) {
resolve(45);
}
};
(async function () {
// Because await wraps the followed expression into 'Promise.resolve'
// it will work with any thenable object (i.e. an object with a .then() method).
var random = await thenable;
console.log(random); // 45
})();
- Stage 3 Draft - Async Functions
- MDN - async function
- PonyFoo - Understanding JavaScript’s async await
- Nicolás Bevacqua for this [PonyFoo - Understanding JavaScript’s async await] (https://ponyfoo.com/articles/understanding-javascript-async-await)
- MDN documentation