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 how to avoid race conditions when running multiple promises and more.
Assignments that can Lead to Race Conditions Because of yield and await
When using promises, it’s easy to create race conditions when we aren’t combining multiple promises properly.
For instance, if we have:
let total = 0;
const getNum = async (num) => {
return num;
}
const add = async (num) => {
total += await getNum(num);
}
Promise.all([add(1), add(2)]).then(() => {
console.log(total);
});
Then we’re calling the add
async function, which gets the result from the getNum
promise and add it to the total
.
Then we call Promise.all
, which runs the code in parallel. In that time, the first value assigned to total
will be overwritten because total
is still set to the initial value in each promise before they were resolved.
Therefore, we’ll see 2 instead of 3 as we expect.
We can fix this mistake by assigning the resolved value in add
to a variable before adding them to the total
as follows:
const add = async (num) => {
const res = await getNum(num);
total += res;
}
Or we can just wait for both to resolve and then add the results together:
const getNum = async (num) => {
return num;
}
const add = async (num) => {
return await getNum(num);
}
Promise.all([add(1), add(2)]).then((nums) => {
const total = nums.reduce((a, b) => a + b, 0);
console.log(total);
});
Not Using isNaN to Check for NaN
NaN
is a special value in JavaScript. It doesn’t equal itself when compared with ==
or ===
. For instance, the following:
NaN === NaN
NaN == NaN
returns false
and:
NaN !== NaN
NaN != NaN
returns true
.
NaN
check also doesn’t work with indexOf
and lastIndexOf
:
const arr = [NaN];
console.log(arr.indexOf(NaN));
console.log(arr.`lastIndexOf`(NaN));
We’ll get -1 returned from the console log in the code above.
Therefore, we should check for NaN
with the isNaN
function as follows:
const foo = NaN;
if (isNaN(foo)){
console.log('is NaN');
}
We can also use the Object.is
method to check for NaN
as follows:
const foo = NaN;
if (Object.is(foo, NaN)) {
console.log('is NaN');
}
To check if NaN
is in an array, we can write our own function to do it:
const arr = [NaN];
console.log(arr.some(a => isNaN(a)));
Compare typeof Expressions Against Invalid Strings
It’s easy to make typos when writing strings. Therefore, we should check if the types that we check with typeof
are correct.
For instance, the following code has typos in the string that’ll cause typeof
to not work as we expect them to:
typeof foo === "strnig"
typeof foo == "undefimed"
typeof bar === "nunber"
typeof bar === "fucntion"
We should make sure that the strings that we compare against are spelled correctly as follows:
typeof foo === "string"
typeof foo == "undefined"
typeof bar === "number"
typeof bar === "function"
Getters and Setters should be Together in Object and Classes
A setter isn’t very useful without its corresponding getter. For instance, if we have:
const obj = {
set foo(val) {
this._foo = val
}
}
Then there’s no way to get the value of foo
, and we’ll get undefined
when we reference obj.foo
even after we set the value.
Therefore, we should add a corresponding getter to the object to return a value. For instance, we should instead write:
const obj = {
get foo() {
return this._foo;
},
set foo(val) {
this._foo = val
}
}
To define a new property with defineProperty
with getters and setters, we can write the following:
const obj = {};
Object.defineProperty(obj, 'foo', {
get() {
return this._foo;
},
set(val) {
this._foo = val
}
});
Then if we assign a value to foo
, we can get the value of the foo
property. Likewise, we can do the same for classes as follows:
class Obj {
get foo() {
return this._foo;
}
set foo(val) {
this._foo = val;
}
}
const obj = new Obj();
obj.foo = 1;
This works the same as the object example, except that we’ve to instantiate it with new
first.
Conclusion
We shouldn’t do assignment with operations like +=
with await
or yield
as the right operand as the value will be overwritten instead of combined with the existing value.
Also, we should use isNaN
to check for NaN
instead of checking with the ===
or ==
operator.
When using typeof
, we should check for typos in the strings that we check for the types against.
Setters without getters also isn’t very useful since we can’t get the value that we set.