JavaScript is a very forgiving language. It’s easy to write code that runs but has mistakes in it.
In this article, we’ll look at some JavaScript mistakes, including loops and promises.
Wrong For Loop Direction
A for
loop with an ending condition that’ll never be reached is probably buggy code. If we want to make an infinite loop, we should use a while
loop as the convention.
For instance, if we have:
for (let i = 0; i < 20; i--) {
}
It’s probably a mistake because we specified the ending condition, but never reach it.
We probably meant:
for (let i = 0; i < 20; i++) {
}
Getters That Don’t Return Anything
If we make a getter but it doesn’t return anything, it’s most likely a mistake. There’s no reason to make a getter that returns undefined
.
For instance, the following is probably incorrect:
let person = {
get name() {}
};
Object.defineProperty(person, "gender", {
get() {}
});
class Person {
get name() {}
}
We have useless getters in all of the code above. If we have a getter, then we should return something in it:
let person = {
get name() {
return 'Jane';
}
};
Object.defineProperty(person, "gender", {
get() {
return 'female';
}
});
class Person {
get name() {
return 'James';
}
}
Async Function as a Promise Executor Callback
When we define a promise from scratch, we have to pass in an executor callback function with the resolve
and reject
functions as parameters.
We don’t want async
functions as executors because when errors are thrown, they’ll be lost and won’t cause the newly constructed promise to reject. This makes debugging and handling some errors hard.
If a promise executor function is using await
, then it’s usually a sign that creates a new Promise
instance is useless or the scope of the new
Promise constructor can be used.
What is using await
is already a promise and async
functions also return a promise, so we don’t need a promise inside a promise.
For example, if we have the following code:
const fs = require('fs');
const foo = new Promise(async (resolve, reject) => {
fs.readFile('foo.txt', (err, result)=> {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
const result = new Promise(async (resolve, reject) => {
resolve(await foo);
});
Then it’s probably a mistake because we’re nesting a promise inside a promise.
What we actually want to do is:
const fs = require('fs');
const foo = new Promise(async (resolve, reject) => {
fs.readFile('foo.txt', (err, result)=> {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
const result = Promise.resolve(foo);
Photo by Matthew Henry on Unsplash
No Await Inside Loops
async
and await
allows for parallelization. Usually, we want to run Promise.all
to run unrelated promises in parallel. Running await
in a loop will run each promise in sequence. This isn’t necessary for promises that don’t depend on each other.
For instance, we should write:
const bar = (results) => console.log(results);
const foo = async (arr) => {
const promises = [];
for (const a of arr) {
promises.push(Promise.resolve(a));
}
const results = await Promise.all(promises);
bar(results);
}
Instead of:
const bar = (results) => console.log(results);
const foo = async (arr) => {
const results = [];
for (const a of arr) {
results.push(await Promise.resolve(a));
}
bar(results);
}
The first example is a lot faster than the second since we’re running them in parallel instead of sequentially.
If the promises are dependent on each other, then we should use something like the 2nd example.
Don’t Compare Anything Against Negative Zero
Comparing again negative zero will return true
for both +0 and -0. We probably actually want to use Object.is(x, -0)
to check if something is equal to -0.
For instance, in the following code:
const x = +0;
const y = -0;
console.log(x === -0)
console.log(y === -0)
Both expressions will log true
. On the other hand, if we use Object.is
as follows:
const x = +0;
const y = -0;
console.log(Object.is(x, -0))
console.log(Object.is(y, -0))
Then the first log is false
and the second is true
, which is probably what we want.
Conclusion
There’re many ways to write code that works unintentionally with JavaScript. To prevent bugs from occurring, we should use Object.is
to compare again +0 and -0, running promises inside promise executor callbacks, adding getters that don’t return anything, or creating infinite loops unintentionally.
If promises can be run in parallel, then we should take advantage of that by using Promise.all
.