Categories
JavaScript JavaScript Basics

Using the JavaScript Math Object for Calculations

In JavaScript, the Math object is a built-in object that lets us do various mathematical operations.

It’s a non-function object with mathematical constants and functions to calculate various quantities. It only works with variables with the number type, and it doesn’t work with BigInt data.

The Math object isn’t a constructor. All the methods are static. We can refer to the members with the dot notation like Math.PI to get the value of Pi, or call the cosine function with Math.cos(x).

Constants are defined with full precision allowing for real numbers in JavaScript.


Constants

The Math object has the following constants:

Math.E

Euler’s constant and the base of natural logarithms, approximately 2.718281828459045.

Math.LN2

Natural logarithm of 2, approximately 0.6931471805599453.

Math.LN10

Natural logarithm of 10, approximately 2.302585092994046.

Math.LOG2E

Base 2 logarithm of E, approximately 1.4426950408889634.

Math.LOG10E

Base 10 logarithm of E, approximately 0.4342944819032518.

Math.PI

The ratio of the circumference of a circle to its diameter, approximately 3.141592653589793.

Math.SQRT1_2

The square root of 1/2, approximately 0.7071067811865476.

Math.SQRT2

The square root of 2, approximately 1.4142135623730951.


Methods

The Math object has many methods, like trigonometric functions, power functions, and logarithmic functions.

The trigonometric functions like sin(), cos(), tan(), asin(), acos(), atan(), atan2() expect angles in radians as arguments and also return angles in radians.

To convert radians to degrees, divide by (Math.PI / 180), and multiply by this to convert the other way.

The precision of the results are browser-dependent because each browser engine implements floating-point calculations differently. This means that different browsers may return different results for the same function.

Math.abs(x)

Returns the absolute value of a number. For example, we can use it as follows:

Math.abs(2) // returns 2  
Math.abs(-2) // returns 2

Math.acos(x)

Returns the inverse cosine of a number. For example, we can use it as follows:

Math.acos(1) // returns 0  
Math.acos(-1) // returns Math.PI

Math.acosh(x)

Returns the inverse hyperbolic cosine of a number. For example, we can use it as follows:

Math.acosh(1) // returns 0

Math.asin(x)

Returns the inverse sine of a number. For example, we can use it as follows:

Math.asin(1) // returns 1.5707963267948966  
Math.asin(0) // returns 0

Math.asinh(x)

Returns the inverse hyperbolic sine of a number. For example, we can use it as follows:

Math.asin(1) // returns 0.881373587019543  
Math.asin(0) // returns 0

Math.atan(x)

Returns the inverse tangent of a number. For example, we can use it as follows:

Math.atan(1) // returns 0.7853981633974483  
Math.atan(0) // returns 0

Math.atanh(x)

Returns the inverse hyperbolic tangent of a number. For example, we can use it as follows:

Math.atanh(1) // returns Infinity  
Math.atanh(0) // returns 0

Math.atan2(y, x)

Returns the inverse tangent of the quotient of its arguments. For example, we can use it as follows:

Math.atan2(1, 1) // returns 0.7853981633974483  
Math.atan2(1, Math.SQRT2) // returns 0.9553166181245093

Math.cbrt(x)

Returns the cube root of a number. For example, we can use it as follows:

Math.cbrt(3) // returns 1.4422495703074083

Math.ceil(x)

Returns the smallest integer greater than or equal to a number. For example, we can use it as follows:

Math.ceil(1.5) // returns 2

Math.clz32(x)

Returns the number of leading zeroes of a 32-bit integer when x is converted to binary. For example, if we write:

Math.clz32(1)

We get 31 because the 1s digit is 1 and the other 31 digits before it are all 0. And if we write:

Math.clz32(2)

We get 30 because the 1s digit is 0, the 2s digit is 1, and the other 30 digits before it are all 0.

Math.cos(x)

Returns the cosine of a number. For example, we can use it as follows:

Math.cos(0) // returns 1

Math.cosh(x)

Returns the hyperbolic cosine of a number. For example, we can use it as follows:

Math.cosh(0) // returns 1

Math.exp(x)

Returns e to the power of x, where x is the argument, and e is Euler’s constant which is approximately 2.718281828459045, and it’s the base of the natural logarithm. We can use it as in the following code:

Math.exp(10) // returns 22026.465794806718

Math.expm1(x)

Returns Math.exp(x) with 1 subtracted from it. For example, we can use it as in the following code:

Math.expm1(1) // returns 1.718281828459045

Math.floor(x)

Returns the largest integer less than or equal to a number. For example, we can use it as in the following code:

Math.floor(1.1) // returns 1

Math.fround(x)

Returns the nearest single-precision float representation of a number.

A single-precision number has 32 bits, with the first bit used for the sign, the next 8 bits used for the exponent, and the remaining 23 bits are the fractional parts of the logarithm, also called the mantissa.

For example, we can use it as in the following code:

Math.fround(1.2) // returns 1.2000000476837158

Math.hypot(a,b,c,…)

Returns the square root of the sum of squares of its arguments. It takes an infinite number of arguments. For example, we can use it as follows:

Math.hypot(1,2)  // returns 2.23606797749979  
Math.hypot(1,2,3,4,5)  // returns 7.416198487095663

Math.imul(x, y)

Returns the result of 32-bit integer multiplication. It rounds off decimals to the nearest integer before performing the multiplication. For example, we can use it as follows:

Math.imul(1,2) // returns 2  
Math.imul(3,4.1) // returns 12

Math.log(x)

Returns the natural logarithm (log with base e, also ln) of a number. For example, we can use it as the following code:

Math.log(Math.E) // returns 1  
Math.log(1) // returns 0

Math.log1p(x)

Returns the natural logarithm (log with base e, also ln) of 1 + x for a number x. For example, we can use it like the following code:

Math.log1p(Math.E - 1) // returns 1  
Math.log1p(1) // returns 0.6931471805599453

Math.log10(x)

Returns the base 10 logarithm of a number. For example, we can use it like the following code:

Math.log10(1) // returns 0  
Math.log10(10) // returns 1

Math.log2(x)

Returns the base 2 logarithm of a number. For example, we can use it like the following code:

Math.log2(2) // returns 0  
Math.log2(10) // returns 3.321928094887362

Math.max(a,b,…)

Returns the largest of zero or more numbers. If nothing is passed in, -Infinity is returned. For example, we can use it like the following code:

Math.max(1, 2) // returns 2  
Math.max(1,2,3,4,5) // returns 5  
Math.max() // returns -Infinity

Math.min(a,b,…)

Returns the smallest of zero or more numbers. If nothing is passed in, Infinity is returned. For example, we can use it like the following code:

Math.min(1,2) // returns 1  
Math.min(1,2,3,4,5) // returns 1  
Math.min() // returns Infinity

Math.pow(x, y)

Returns base to the exponent power. For example, we can use it like the following code:

Math.pow(1,2) // returns 1  
Math.min(2,3) // returns 8

Math.random()

Returns a pseudo-random number between 0 and 1. For example, we can use it as the following code:

Math.random() // returns 0.5275086314071882 or any other number between 0 and 1

Math.round(x)

Returns the value of a number rounded to the nearest integer. For example, we can use it as the following code:

Math.round(1.2) // returns 1

Math.sign(x)

Returns the sign of the x, indicating whether x is positive, negative, or zero. If x is positive, 1 is returned. If an x is negative, then -1 is returned. If x is 0 then 0 is returned. For example, we can use it like in the following code:

Math.sign(1) // returns 1  
Math.sign(3) // returns 1  
Math.sign(-1) // returns -1  
Math.sign(-3) // returns -1  
Math.sign(0) // returns 0

Math.sin(x)

Returns the sine of a number. For example, we can use it as in the following code:

Math.sin(1) // returns 0.8414709848078965

Math.sinh(x)

Returns the hyperbolic sine of a number. For example, we can use it as in the following code:

Math.sinh(1) // returns 1.1752011936438014

Math.sqrt(x)

Returns the positive square root of a number. For example, we can use it like in the following code:

Math.sqrt(1) // returns 1  
Math.sqrt(2) // returns 1.4142135623730951

Math.tan(x)

Returns the tangent of a number. For example, we can use it as in the following code:

Math.tan(1) // returns 1.5574077246549023

Math.tanh(x)

Returns the hyperbolic tangent of a number. For example, we can use it as in the following code:

Math.tanh(2) // returns 0.9640275800758169

Math.trunc(x)

Returns the integer part of the number x, removing any fractional digits. For example, we can use it as in the following code:

Math.trunc(2.2223) // returns 2

Extending the Math Object

We can extend the Math object by adding custom properties and methods to it since it’s not a constructor. For instance, we can add the following function to the Math object to calculate the fifth root of a number:

Math.fifthRoot = (x) => Math.pow(x, 1/5);

The Math object is handy for making calculations in JavaScript. It has some useful constants and methods for calculations of common math operations like taking the logarithm and trigonometric functions.

We can extend the Math object easily by attaching methods and properties to it directly.

Categories
JavaScript JavaScript Basics

Comparing Non-English Strings with JavaScript Collators

With the combination of the double equal or triple equal operator with string methods, we can compare strings easily in a case-sensitive or case insensitive manner. However, this doesn’t take into account the characters that are in non-English strings like French or Italian. These languages have alphabets that may contain accents, something that isn’t recognized in normal string comparisons.

To handle this scenario, we can use the Intl.Collator object to compare strings with accents or for different locales. The Intl.Collator object is a constructor for collators, which are objects that let us compare characters in a language-sensitive way. With Collators, we can compare the order of single characters according to the language that is specified.

Basic Collator Usage for String Equality Comparison

To use a collator, we can construct a Collator object and then use its compare method. The compare method does a comparison of the alphabetical order of the entire string based on the locale. For example, if we want to compare two strings in the German using its alphabet’s order, we can write the following code:

const collator = new Intl.Collator('de');  
const order = collator.compare('Ü', 'ß');  
console.log(order);

We created the Collator object by writing new Intl.Collator(‘de’) to specify that we are comparing strings in the German alphabet. Then we use the created compare method, which takes two parameters as the two strings that you want to compare in string form.

Then a number is returned from the compare function. 1 is returned if the string in the first parameter comes after the second one alphabetically, 0 if both strings are the same, and -1 is returned if the string in the first parameter comes before the second string alphabetically.

So if we flip the order of the strings like in the code below:

const collator = new Intl.Collator('de');  
const order = collator.compare('ß', 'Ü');  
console.log(order);

Then the console.log outputs -1.

If they’re the same, like in the following code:

const collator = new Intl.Collator('de');  
const order = collator.compare('ß', 'ß');  
console.log(order);

Then we get 0 returned for order.

To summarize: If the strings are equal, the function returns 0. If they are not equal the function returns either 1 or -1 which also indicates the alphabetical order of the strings.

Advanced Usage

The Collator is useful because we can put it in the Array.sort method as a callback function to sort multiple strings in the array. For example, if we have multiple German strings in an array, like in the code below:

const collator = new Intl.Collator('de');  
const sortedLetters = ['Z', 'Ä', 'Ö', 'Ü', 'ß'].sort(collator.compare);  
console.log(sortedLetters);

Then we get [“Ä”, “Ö”, “ß”, “Ü”, “Z”].

The constructor takes a number of options that take into account the features of the alphabets of different languages. As we can see above, the first parameter in the constructor is the locale, which is BCP-47 language tag, or an array of such tags. This is an optional parameter. An abridged list of BCP-47 language tags include:

  • ar — Arabic
  • bg — Bulgarian
  • ca — Catalan
  • zh-Hans — Chinese, Han (Simplified variant)
  • cs — Czech
  • da — Danish
  • de — German
  • el — Modern Greek (1453 and later)
  • en — English
  • es — Spanish
  • fi — Finnish
  • fr — French
  • he — Hebrew
  • hu — Hungarian
  • is — Icelandic
  • it — Italian
  • ja — Japanese
  • ko — Korean
  • nl — Dutch
  • no — Norwegian
  • pl — Polish
  • pt — Portuguese
  • rm — Romansh
  • ro — Romanian
  • ru — Russian
  • hr — Croatian
  • sk — Slovak
  • sq — Albanian
  • sv — Swedish
  • th — Thai
  • tr — Turkish
  • ur — Urdu
  • id — Indonesian
  • uk — Ukrainian
  • be — Belarusian
  • sl — Slovenian
  • et — Estonian
  • lv — Latvian
  • lt — Lithuanian
  • tg — Tajik
  • fa — Persian
  • vi — Vietnamese
  • hy — Armenian
  • az — Azerbaijani
  • eu — Basque
  • hsb — Upper Sorbian
  • mk — Macedonian
  • tn — Tswana
  • xh — Xhosa
  • zu — Zulu
  • af — Afrikaans
  • ka — Georgian
  • fo — Faroese
  • hi — Hindi
  • mt — Maltese
  • se — Northern Sami
  • ga — Irish
  • ms — Malay (macrolanguage)
  • kk — Kazakh
  • ky — Kirghiz
  • sw — Swahili (macrolanguage)
  • tk — Turkmen
  • uz — Uzbek
  • tt — Tatar
  • bn — Bengali
  • pa — Panjabi
  • gu — Gujarati
  • or — Oriya
  • ta — Tamil
  • te — Telugu
  • kn — Kannada
  • ml — Malayalam
  • as — Assamese
  • mr — Marathi
  • sa — Sanskrit
  • mn — Mongolian
  • bo — Tibetan
  • cy — Welsh
  • km — Central Khmer
  • lo — Lao
  • gl — Galician
  • kok — Konkani (macrolanguage)
  • syr — Syriac
  • si — Sinhala
  • iu — Inuktitut
  • am — Amharic
  • tzm — Central Atlas Tamazight
  • ne — Nepali
  • fy — Western Frisian
  • ps — Pushto
  • fil — Filipino
  • dv — Dhivehi
  • ha — Hausa
  • yo — Yoruba
  • quz — Cusco Quechua
  • nso — Pedi
  • ba — Bashkir
  • lb — Luxembourgish
  • kl — Kalaallisut
  • ig — Igbo
  • ii — Sichuan Yi
  • arn — Mapudungun
  • moh — Mohawk
  • br — Breton
  • ug — Uighur
  • mi — Maori
  • oc — Occitan (post 1500)
  • co — Corsican
  • gsw — Swiss German
  • sah — Yakut
  • qut — Guatemala
  • rw — Kinyarwanda
  • wo — Wolof
  • prs — Dari
  • gd — Scottish Gaelic

For example, de is for German or fr-ca for Canadian French. So, we can sort Canadian French strings by running the following code:

const collator = new Intl.Collator('fr-ca');  
const sortedLetters = ['ç', 'à', 'c'].sort(collator.compare);  
console.log(sortedLetters);

The constructor to Collator can also take an array of strings for multiple locale comparison — new Intl.Collator([/* local strings */]). The array argument allows us to sort strings from multiple locales. For example, we can sort both Canadian French alphabet and the German alphabet at the same time:

const collator = new Intl.Collator(['fr-ca', 'de']);  
const sortedLetters = [  
  'Ü', 'ß', 'ç', 'à', 'c'  
].sort(collator.compare);
console.log(sortedLetters);

Then we get [“à”, “c”, “ç”, “ß”, “Ü”] from the console.log statement.

Additional Options

Unicode extension keys which include "big5han", "dict", "direct", "ducet", "gb2312", "phonebk", "phonetic", "pinyin", "reformed", "searchjl", "stroke", "trad", "unihan" are also allowed in our locale strings. They specify the collations that we want to compare strings with. However, when there are fields in the options in the second argument that overlaps with this, then the options in the argument overrides the Unicode extension keys specified in the first argument.

Numerical collations can be specified by adding kn to your locale string in your first argument. For example, if we want to compare numerical strings, then we can write:

const collator = new Intl.Collator(['en-u-kn-true']);  
const sortedNums = ['10', '2'].sort(collator.compare);  
console.log(sortedNums);

Then we get [“2”, “10”] since we specified kn in the locale string in the constructor which makes the collator compare numbers.

Also, we can specify whether upper or lower case letters should be sorted first with the kf extension key. The possible options are upper, lower, or false. false means that the locale’s default will be the option. This option can be set in the locale string by adding as a Unicode extension key, and if both are provided, then the option property will take precedence. For example, to make uppercase letters have precedence over lowercase letters, we can write:

const collator = new Intl.Collator('en-ca-u-kf-upper');  
const sorted = ['Able', 'able'].sort(collator.compare);  
console.log(sorted);

This sorts the same word with upper case letters first. When we run console.log, we get [“Able”, “able”] since we have an uppercase ‘A’ in ‘Able’, and a lowercase ‘a’ for ‘able’. On the other hand, if we instead pass in en-ca-u-kf-lower in the constructor like in the code below:

const collator = new Intl.Collator('en-ca-u-kf-lower');  
const sorted = ['Able', 'able'].sort(collator.compare);  
console.log(sorted);

Then after console.log we get [“able”, “Able”] because kf-lower means that we sort the same word with lowercase letters before the ones with uppercase letters.

The second argument of the constructor takes an object that can have multiple properties. The properties that the object accepts are localeMatcher, usage, sensitivity, ignorePunctuation, numeric, and caseFirst. numeric is the same as the kn option in the Unicode extension key in the locale string, and caseFirst is the same as the kf option in the Unicode extension key in the locale string. The localeMatcher option specifies the locale matching algorithm to use. The possible values are lookup and best fit. The lookup algorithm searches for the locale until it finds the one that fits the character set of the strings that are being compared. best fit finds the locale that is at least but possibly more suited that the lookup algorithm.

The usage option specifies whether the Collator is used for sorting or searching for strings. The default option is sort.

The sensitivity option specifies the way that the strings are compared. The possible options are base, accent, case, and variant.

base compares the base of the letter, ignoring the accent. For example a is not the same as b, but a is the same as á, a is the same as Ä.

accent specifies that a string is only different if there is a base letter or their accents are unequal then they’re unequal, ignoring case. So a isn’t the same as b, but a is the same as A. a is not the same as á.

The case option specifies that strings that are different in their base letters or case are considered unequal, so a wouldn’t be the same as A and a wouldn’t be the same as c, but a is the same as á.

variant means that strings that are different in the base letter, accent, other marks, or case are considered unequal. For example a wouldn’t be the same as A and a wouldn’t be the same as c. But also a wouldn’t be the same as á.

The ignorePunctuation specifies whether punctuation should be ignored when sorting strings. It’s a boolean property and the default value is false.

We can use the Collator constructor with the second argument in the following way:

const collator = new Intl.Collator('en-ca', {  
  ignorePunctuation: false,  
  sensitivity: "base",  
  usage: 'sort'  
});  
console.log(collator.compare('Able', 'able'));

In the code above, we sort by checking for punctuation and only consider letters different if the base letter is different, and we keep the default that upper case letters are sorted first, so we get [‘Able’, ‘able’] in the console.log.

We can search for strings as follows:

const arr = ["ä", "ad", "af", "a"];  
const stringToSearchFor = "af";
const collator = new Intl.Collator("fr", {  
  usage: "search"  
});  
const matches = arr.filter((str) => collator.compare(str, stringToSearchFor) === 0);  
console.log(matches);

We set the usage option to search to use the Collator to search for strings and when the compare method returns 0, then we know that we have the same string. So we get [“af”] logged when we run console.log(matches).

We can adjust the options for comparing letter, so if we have:

const arr = ["ä", "ad", "ef", "éf", "a"];  
const stringToSearchFor = "ef";
const collator = new Intl.Collator("fr", {  
  sensitivity: 'base',  
  usage: "search"  
});
const matches = arr.filter((str) => collator.compare(str, stringToSearchFor) === 0);
console.log(matches);

Then we get [“ef”, “éf”] in our console.log because we specified sensitivity as base which means that we consider the letters with the same base accent as the same.

Also, we can specify the numeric option to sort numbers. For example, if we have:

const collator = new Intl.Collator(['en-u-kn-false'], {  
  numeric: true  
});  
const sortedNums = ['10', '2'].sort(collator.compare);  
console.log(sortedNums);

Then we get [“2”, “10”] because the numeric property in the object in the second argument overrides the kn-false in the first argument.

Conclusion

JavaScript offers a lot of string comparison options for comparing strings that aren’t in English. The Collator constructor in Intl provides many options to let us search for or sort strings in ways that can’t be done with normal comparison operators like double or triple equals. It lets us order numbers, and consider cases, accents, punctuation, or the combination of those features in each character to compare strings. Also, it accepts locale strings with key extensions for comparison.

All of these options together make JavaScript’s Collator constructor a great choice for comparing international strings.

Categories
JavaScript JavaScript Basics

Using JavaScript BigInt to Represent Large Numbers

To represent integers larger than 2**53 – 1 in JavaScript, we can use the BigInt object to represent the values.

It can be manipulated via normal operations like arithmetic operators — addition, subtraction, multiplication, division, remainder, and exponentiation.

It can be constructed from numbers and hexadecimal or binary strings. Also, it supports bitwise operations like AND, OR, NOT, and XOR. The only bitwise operation that doesn’t work is the zero-fill right-shift operator (>>> ) because BigInts are all signed.

Also, the unary + operator isn’t supported to not break asm.js. These operations are only done when all the operands are BigInts. We can’t have some operands being BigInts and some being numbers.

In JavaScript, a BigInt is not the same as a normal number. It’s distinguished from a normal number by having an n at the end of the number. We can define a BigInt with the BigInt factory function.

It takes one argument that can be an integer number or a string representing a decimal integer, hexadecimal string, or binary string. BigInt cannot be used with the built-in Math object.

Also, when converting between number and BigInt and vice versa, we have to be careful because the precision of BigInt might be lost when a BigInt is converted into a number.


Usage

To define a BigInt, we can write the following if we want to pass in a whole number:

const bigInt = BigInt(1);  
console.log(bigInt);

It would log 1n when we run the console.log statement. If we want to pass a string into the factory function instead, we can write:

const bigInt = BigInt('2222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222');  
console.log(bigInt);

We can also pass an hexadecimal number string with a string that starts with 0x into the factory function:

const bigHex = BigInt("0x1fffffffffffff111111111");  
console.log(bigHex);

The code above would log 618970019642690073311383825n when we run console.log on bigHex.

Likewise, we can pass in a binary string with a string that starts with 0b and binary string in the remainder of the string to get a BigInt:

const bigBin = BigInt("0b111111111111111000000000011111111111111111111111");  
console.log(bigBin);

The code above would get us 281466395164671n when we run console.log on it. Passing in strings would be handy if the BigInt that we want to create is outside of the range that can be accepted by the number type.

We can also define a BigInt with a BigInt literal, we can just attach an n character to the whole number. For example, we can write:

const bigInt = 22222222222222222222222222222222n;  
console.log(bigInt);

Then, we get 22222222222222222222222222222222n as the value of bigInt when we log the value.

BigInt has its own data type in JavaScript. When you run the typeof operator on a BigInt variable, constant or value, we would get bigint. For example, when we run:

typeof 2n === 'bigint';  
typeof BigInt(2) === 'bigint';

Both lines of code would evaluate to true. However, if we wrap it with the Object factory function, we get that the type is an object:

typeof Object(2n) === 'object';

The code above would evaluate to true.

We can apply number operations to BigInts.

This includes the arithmetic operators — addition, subtraction, multiplication, division, remainder, and exponentiation. Also, it supports bitwise operations like AND, OR, NOT, and XOR.

The only bitwise operation that doesn’t work is the zero-fill right-shift operator (>>> ) because BigInts are all signed. Also, the unary + operator isn’t supported to not break asm.js.

These operations are only done when all the operands are BigInts. We can’t have some operands being BigInts and some being numbers. For example, if we have:

const bigInt = BigInt(Number.MAX_SAFE_INTEGER);  
console.log(bigInt);
const biggerInt = bigInt + BigInt(1);  
console.log(biggerInt);
const evenBiggerInt = bigInt + BigInt(2);  
console.log(evenBiggerInt);
const multi = bigInt * BigInt(2);  
console.log(multi);
const subtr = bigInt - BigInt(10);  
console.log(subtr);
const remainder = bigInt % BigInt(1);  
console.log(remainder);
const bigN = bigInt ** BigInt(54);  
console.log(bigN);
const veryNegativeNum = bigN * -BigInt(1)  
console.log(veryNegativeNum);

We get 9007199254740991n for bigInt, 9007199254740992n for biggerInt, 9007199254740993n for evenBiggerInt, 18014398509481982n for multi, 9007199254740981n for subtr, 0n for remainder, 3530592467533273200243077170885155617107348521747142286627863260349958518655132050034081285541183004983189865471543609006121689601641796259277395721973496380268998810860889580999688899063966604079229944616948651888866122410855207004436640389001057851295873774080462273415460559916461808220601907673652198075821210257903676343961872269549414664419834643799298966710366275919846143068708381391506113181640387818197335712192797007703730122048543818655729529755334964590919971124632271934272078761071878238334159341746985273963326734748343552398522547662400805644304911487571159654254814460707275228515191584712593238883953404971043549757327554636466197405269908317698331974392008288867249576664013677011521696874812515379689360830743272800013459321098384864332719963035293422648481458040217707301509007592199565531403472471705983351384755965631442881685949576642561n for bigN, and -3530592467533273200243077170885155617107348521747142286627863260349958518655132050034081285541183004983189865471543609006121689601641796259277395721973496380268998810860889580999688899063966604079229944616948651888866122410855207004436640389001057851295873774080462273415460559916461808220601907673652198075821210257903676343961872269549414664419834643799298966710366275919846143068708381391506113181640387818197335712192797007703730122048543818655729529755334964590919971124632271934272078761071878238334159341746985273963326734748343552398522547662400805644304911487571159654254814460707275228515191584712593238883953404971043549757327554636466197405269908317698331974392008288867249576664013677011521696874812515379689360830743272800013459321098384864332719963035293422648481458040217707301509007592199565531403472471705983351384755965631442881685949576642561n for veryNegativeNum.

Note that when we get fractional results, the fractional parts will be truncated when result has it. BigInt is a big integer and it’s not for storing decimals. For example, in the examples below:

const expected = 8n / 2n;  
console.log(expected)const rounded = 9n / 2n;  
console.log(rounded)

We get 4n for expected and rounded. This is because the fractional part is removed from the BigInt.

Comparison operators can be applied to BigInts. The operands can be either BigInt or numbers. We can use the bigger than, bigger than or equal to, less than, less than or equal to, double equals, and triple equals operators with BigInts.

For example, we can compare 1n to 1:

1n === 1

The code above would evaluate to false because BigInt and numbers aren’t the same types. But when we replace triple equals with double equals, like in the code below:

1n == 1

The statement above evaluates to true because only the value is compared.

Note that in both examples, we mixed BigInt operands with number operands. This is allowed for comparison operators.

BigInts and numbers can be compared together with other operators as well, like in the following examples:

1n < 9  
// true9n > 1  
// true9 > 9n  
// false  
  
9n > 9  
// false  
  
9n >= 9  
// true

Also, they can be mixed together in one array and sorted together. For example, if we have the following code:

const mixedNums = [5n, 6, -120n, 12, 24, 0, 0n];  
mixedNums.sort();  
console.log(mixedNums)

We get [-120n, 0, 0n, 12, 24, 5n, 6]. We also sort it in descending order with the following code:

const mixedNums = [5n, 6, -120n, 12, 24, 0, 0n];  
mixedNums.sort((a, b) => {  
  if (a > b) {  
    return -1  
  } else if (a < b) {  
    return 1  
  }  
  return 0  
});  
console.log(mixedNums)

In the code above, we used the comparison operators to compare the value of the numbers and return -1 if the first number is bigger than the second number, return 1 if the first number is less than the second number, and return 0 otherwise.

If we wrap BigInts with Object, then they’re compared as objects, so two objects are only considered the same if the same instance is referenced. For example, if we have:

0n === Object(0n);  
Object(0n) === Object(0n);  
const o = Object(0n);  
o === o;

Then the first three lines in the code above would evaluate to false while the last line would evaluate to true.

When BigInts are coerced into booleans, they act the same as if they’re numbers. For example, Boolean(0n) would return false, and anything else would return true.

For example, we can coerce them into booleans like in the following code:

0n || 11n  
// 11n  
  
0n && 11n  
// 0n  
  
Boolean(0n)  
// false  
  
Boolean(11n)  
// true  
  
!11n  
// false  
  
!0n  
// true

BigInt Methods

The BigInt object has several methods. There are the static asIntN() and asUintN() methods, and the toLocaleString(), toString(), and valueOf() instance methods.

The asIntN method wraps a BigInt value between -2 to the width minus 1 and 2 to the width minus 1. For example, if we have:

const bigNum = 2n ** (62n - 1n) - 1n;  
console.log(BigInt.asIntN(62, bigNum));

Then we get the literal of the actual value of bigNum modulo 62 returned, which is 2305843009213693951n. However, if we have:

const bigNum = 2n ** (63n - 1n) - 1n;  
console.log(BigInt.asIntN(62, bigNum));

We get -1n since the 2n ** (63n — 1n) modulo 2n ** 63n is returned.

The asUintN() method wraps a BigInt value between 0 and 2 to the width minus 1. For example, if we have:

const bigNum = 2n ** 11n - 1n;  
console.log(BigInt.asUintN(11, bigNum));

Then we get the literal of the actual value of bigNum returned, which is 2305843009213693951n. However, if we have:

const bigNum = 2n ** 11n - 1n;  
console.log(BigInt.asUintN(11, bigNum));

We get 2047n since the 2n ** 11n — 1n modulo 2n ** 11n is returned.

BigInt has a toLocaleString() method to return the value of the string for the BigInt depending on the locale we pass in. For example, if we want to get the French representation of a BigInt, we can write:

const bigNum = 2n ** 60n - 1n;  
console.log(bigNum.toLocaleString('fr'));

The code above will log 1 152 921 504 606 846 975.

The toString() method will convert a BigInt to a string. For example, we can write:

const bigNum = 2n ** 60n - 1n;  
console.log(bigNum.toString());

The code above will log 1152921504606846975.

The valueOf method will get the value of the BigInt object. For example, if we run:

const bigNum = 2n ** 60n - 1n;  
console.log(bigNum.valueOf());

Then we get 1152921504606846975n logged.

BigInts aren’t supported by JSON, so a TypeError would be raised if we try to convert it to JSON with JSON.stringify().

However, we can convert it to something supported like a string, then it can be stored as JSON. We can override the toJSON method of a BigInt by writing:

BigInt.prototype.toJSON = function() {  
  return this.toString();  
}

const bigIntString = JSON.stringify(88888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888n)  
console.log(bigIntString);

Then, we get: “88888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888”.

BigInts are useful for representing integers larger than 2 to the 53rd power minus 1 in JavaScript, we can use the BigInt object to represent the values.

It can be manipulated via normal operations like arithmetic operators — addition, subtraction, multiplication, division, remainder, and exponentiation.

BigInts support most bitwise operations like AND, OR, NOT, and XOR.

They can also be converted from hexadecimal or binary numbers.

These operations are only done when all the operands are BigInts. We can’t have some operands being BigInts and some being numbers.

In JavaScript, a BigInt is not the same as a normal number. It’s distinguished from a normal number by having an n at the end of the number. We can define a BigInt with the BigInt factory function.

It takes one argument that can be an integer number or a string representing a decimal integer, hexadecimal string, or binary string. BigInt cannot be used with the built-in Math object.

Categories
Vue

How to Make a Chrome Extension with Vue.js

The most popular web browsers, Chrome and Firefox support extensions. Extensions are small apps that you can add to your browser to get the functionality that is not included in your browser. This makes extending browser functionality very easy. All a user has to do is to add browser add-ons from the online stores like the Chrome Web Store or the Firefox Store to add browser add-ons.

Browser extensions are just normal HTML apps packages in a specific way. This means that we can use HTML, CSS, and JavaScript to build our own extensions.

Chrome and Firefox extensions follow the Web Extension API standard. The full details are at https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions

In this article, we will make a Chrome extension that displays the weather from the OpenWeatherMap API. We will add a search to let users look up the current weather and forecast from the API and display it in the extension’s popup box.

We will use Vue.js to build the browser extension. To begin building it, we start with creating the project with Vue CLI. Run npx @vue/cli create weather-app to create the project. In the wizard, select Babel and Vuex.

The OpenWeatherMap API is available at https://openweathermap.org/api. You can register for an API key here. Once you got an API key, create an .env file in the root folder and add VUE_APP_APIKEY as the key and the API key as the value.

Next, we use the vue-cli-plugin-browser-extension to add the files for writing and compiling the Chrome extension. The package settings and details are located at https://www.npmjs.com/package/vue-cli-plugin-browser-extension.

To add it to our project, we run vue add browser-extension to add the files needed to build the extension. The command will change the file structure of our project.

After that command is run, we have to remove some redundant files. We should remove App.vue and main.js from the src folder and leave the files with the same name in the popup folder alone. Then we run npm run serve to build the files as we modify the code.

Next, we have to install the Extension Reload to reload the extension as we are changing the files. Install it from https://chrome.google.com/webstore/detail/extensions-reloader/fimgfedafeadlieiabdeeaodndnlbhid to get hot reloading of our extension in Chrome.

Then we go to the chrome://extensions/ URL in Chrome and toggle on Developer Mode. We should see the Load unpacked button on the top left corner. Click that, and then select the dist folder in our project to load our extension into Chrome.

Next, we have to install some libraries that we will use. We need Axios for making HTTP requests, BootstrapVue for styling, and Vee-Validate for form validation. To install them, we run npm i axios bootstrap-vue vee-validate to install them.

With all the packages installed we can start writing our code. Create CurrentWeather.vue in the components folder and add:

<template>  
  <div>  
    <br />  
    <b-list-group v-if="weather.main">  
      <b-list-group-item>Current Temparature: {{weather.main.temp - 273.15}} C</b-list-group-item>  
      <b-list-group-item>High: {{weather.main.temp_max - 273.15}} C</b-list-group-item>  
      <b-list-group-item>Low: {{weather.main.temp_min - 273.15}} C</b-list-group-item>  
      <b-list-group-item>Pressure: {{weather.main.pressure }}mb</b-list-group-item>  
      <b-list-group-item>Humidity: {{weather.main.humidity }}%</b-list-group-item>  
    </b-list-group>  
  </div>  
</template>

<script>  
import { requestsMixin } from "@/mixins/requestsMixin";

export default {  
  name: "CurrentWeather",  
  mounted() {},  
  mixins: [requestsMixin],  
  computed: {  
    keyword() {  
      return this.$store.state.keyword;  
    }  
  },  
  data() {  
    return {  
      weather: {}  
    };  
  },  
  watch: {  
    async keyword(val) {  
      const response = await this.searchWeather(val);  
      this.weather = response.data;  
    }  
  }  
};  
</script>

<style scoped>  
p {  
  font-size: 20px;  
}  
</style>

This component displays the current weather from the OpenWeatherMap API as the keyword from the Vuex store is updated. We will create the Vuex store later. The this.searchWeather function is from the requestsMixin , which is a Vue mixin that we will create. The computed block gets the keyword from the store via this.$store.state.keyword and return the latest value.

Next, create Forecast.vue in the same folder and add:

<template>  
  <div>  
    <br />  
    <b-list-group v-for="(l, i) of forecast.list" :key="i">  
      <b-list-group-item>  
        <b>Date: {{l.dt_txt}}</b>  
      </b-list-group-item>  
      <b-list-group-item>Temperature: {{l.main.temp - 273.15}} C</b-list-group-item>  
      <b-list-group-item>High: {{l.main.temp_max - 273.15}} C</b-list-group-item>  
      <b-list-group-item>Low: {{l.main.temp_min }}mb</b-list-group-item>  
      <b-list-group-item>Pressure: {{l.main.pressure }}mb</b-list-group-item>  
    </b-list-group>  
  </div>  
</template>

<script>  
import { requestsMixin } from "@/mixins/requestsMixin";

export default {  
  name: "Forecast",  
  mixins: [requestsMixin],  
  computed: {  
    keyword() {  
      return this.$store.state.keyword;  
    }  
  },  
  data() {  
    return {  
      forecast: []  
    };  
  },  
  watch: {  
    async keyword(val) {  
      const response = await this.searchForecast(val);  
      this.forecast = response.data;  
    }  
  }  
};  
</script>

<style scoped>  
p {  
  font-size: 20px;  
}  
</style>

It’s very similar to CurrentWeather.vue. The only difference is that we are getting the current weather instead of the weather forecast.

Next, we create a mixins folder in the src folder and add:

const APIURL = "http://api.openweathermap.org";  
const axios = require("axios");

export const requestsMixin = {  
  methods: {  
    searchWeather(loc) {  
      return axios.get(  
        `${APIURL}/data/2.5/weather?q=${loc}&appid=${process.env.VUE_APP_APIKEY}`  
      );  
    }, 

    searchForecast(loc) {  
      return axios.get(  
        `${APIURL}/data/2.5/forecast?q=${loc}&appid=${process.env.VUE_APP_APIKEY}`  
      );  
    }  
  }  
};

These functions are for getting the current weather and the forecast respectively from the OpenWeatherMap API. process.env.VUE_APP_APIKEY is obtained from our .env file that we created earlier.

Next in App.vue inside the popup folder, we replace the existing code with:

<template>  
  <div>  
    <b-navbar toggleable="lg" type="dark" variant="info">  
      <b-navbar-brand href="#">Weather App</b-navbar-brand>  
    </b-navbar>  
    <div class="page">  
      <ValidationObserver ref="observer" v-slot="{ invalid }">  
        <b-form @submit.prevent="onSubmit" novalidate>  
          <b-form-group label="Keyword" label-for="keyword">  
            <ValidationProvider name="keyword" rules="required" v-slot="{ errors }">  
              <b-form-input  
                :state="errors.length == 0"  
                v-model="form.keyword"  
                type="text"  
                required  
                placeholder="Keyword"  
                name="keyword"  
              ></b-form-input>  
              <b-form-invalid-feedback :state="errors.length == 0">Keyword is required</b-form-invalid-feedback>  
            </ValidationProvider>  
          </b-form-group> 
          <b-button type="submit" variant="primary">Search</b-button>  
        </b-form>  
      </ValidationObserver><br /> 

      <b-tabs>  
        <b-tab title="Current Weather">  
          <CurrentWeather />  
        </b-tab>  
        <b-tab title="Forecast">  
          <Forecast />  
        </b-tab>  
      </b-tabs>  
    </div>  
  </div>  
</template>

<script>  
import CurrentWeather from "@/components/CurrentWeather.vue";  
import Forecast from "@/components/Forecast.vue";

export default {  
  name: "App",  
  components: { CurrentWeather, Forecast },  
  data() {  
    return {  
      form: {}  
    };  
  },  
  methods: {  
    async onSubmit() {  
      const isValid = await this.$refs.observer.validate();  
      if (!isValid) {  
        return;  
      }  
      localStorage.setItem("keyword", this.form.keyword);  
      this.$store.commit("setKeyword", this.form.keyword);  
    }  
  },  
  beforeMount() {  
    this.form = { keyword: localStorage.getItem("keyword") || "" };  
  },  
  mounted(){  
    this.$store.commit("setKeyword", this.form.keyword);  
  }  
};  
</script>

<style>  
html {  
  min-width: 500px;  
}

.page {  
  padding: 20px;  
}  
</style>

We add the BootstrapVue b-navbar here to add a top bar to show the extension’s name. Below that, we added the form for searching the weather info. Form validation is done by wrapping the form in the ValidationObserver component and wrapping the input in the ValidationProvider component. We provide the rule for validation in the rules prop of ValidationProvider . The rules will be added in main.js later.

The error messages are displayed in the b-form-invalid-feedback component. We get the errors from the scoped slot in ValidationProvider . It’s where we get the errors object from.

When the user submits the number, the onSubmit function is called. This is where the ValidationObserver becomes useful as it provides us with the this.$refs.observer.validate() function for form validation.

If isValid resolves to true , then we set the keyword in local storage, and also in the Vuex store by running this.$store.commit(“setKeyword”, this.form.keyword); .

In the beforeMount hook, we set the keyword so that it will be populated when the extension first loads if a keyword was set in local storage. In the mounted hook, we set the keyword in the Vuex store so that the tabs will get the keyword to trigger the search for the weather data.

Then in store.js , we replace the existing code with:

import Vue from "vue";  
import Vuex from "vuex";Vue.use(Vuex);export default new Vuex.Store({  
  state: {  
    keyword: ""  
  },  
  mutations: {  
    setKeyword(state, payload) {  
      state.keyword = payload;  
    }  
  },  
  actions: {}  
});

to add the Vuex store that we referenced in the components. We have the keyword state for storing the search keyword in the store, and the setKeyword mutation function so that we can set the keyword in our components.

Next in popup/main.js , we replace the existing code with:

import Vue from 'vue'  
import App from './App.vue'  
import store from "../store";  
import "bootstrap/dist/css/bootstrap.css";  
import "bootstrap-vue/dist/bootstrap-vue.css";  
import BootstrapVue from "bootstrap-vue";  
import { ValidationProvider, extend, ValidationObserver } from "vee-validate";  
import { required } from "vee-validate/dist/rules";/\* eslint-disable no-new \*/

extend("required", required);  
Vue.component("ValidationProvider", ValidationProvider);  
Vue.component("ValidationObserver", ValidationObserver);  
Vue.use(BootstrapVue);Vue.config.productionTip = false;new Vue({store,  
  render: h => h(App)  
}).$mount("#app");

We added the validation rules that we used in the previous files here, as well as include all the libraries we use in the app. We registered ValidationProvider and ValidationObserver by calling Vue.component so that we can use them in our components. The validation rules provided by Vee-Validate are included in the app so that they can be used by the templates by calling extend from Vee-Validate. We called Vue.use(BootstrapVue) to use BootstrapVue in our app.

Finally in index.html , we replace the existing code with:

<!DOCTYPE html>  
<html lang="en">  
  <head>  
    <meta charset="utf-8" />  
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />  
    <meta name="viewport" content="width=device-width,initial-scale=1.0" />  
    <link rel="icon" href="<%= BASE_URL %>favicon.ico" />  
    <title>Weather App</title>  
  </head>  
  <body>  
    <noscript>  
      <strong  
        >We're sorry but vue-chrome-extension-tutorial-app doesn't work properly  
        without JavaScript enabled. Please enable it to continue.</strong  
      >  
    </noscript>  
    <div id="app"></div>  
    <!-- built files will be auto injected -->  
  </body>  
</html>

to replace the title.

Categories
JavaScript Answers

Using Promises for Async Programming in JavaScript

Because JavaScript is a single-threaded language, synchronous code can only be executed one line at a time. This means that if there’s synchronous code that runs longer than instantaneously, then it will stop the rest of the code from running until whatever is being run is finished. To prevent code that runs for an indeterminate amount of time from holding up other code from running, we need to have asynchronous code.


Promises

To do this in JavaScript, we can use promises in our code. Promises are objects that represent a process that runs for an indeterminate amount of time and may result in success or failure. To create promises in JavaScript, we use a Promise object that’s a constructor for creating promises. The Promise constructor takes a function called the executor function that has the resolve and reject parameters. Both parameters are functions that we call to either fulfill the promise (which means end it successfully with some value) or to reject the promise (which means returning some error value and marking the promise as failed). The function’s return value is ignored. Therefore, promises can’t return anything other than promises.

For example, we can define a promise in JavaScript like in the following code:

const promise = new Promise((resolve, reject) => {  
  setTimeout(() => resolve('abc'), 1000);  
});

The code above creates a promise that fulfills the promise with the value abc after one second. Because we’re running setTimeout inside the executor function to resolve the promise with the value abc in one second, it’s asynchronous code. We can’t return the value abc within the callback function of setTimeout, so we have to call resolve('abc') to get the resolved value back. We can access the resolved value of the fulfilled promise by using the then function. The then function takes a callback function that takes the resolved value of the fulfilled promise as a parameter. You can get the value there and do what you want with it. For example, we can do something like:

const promise = new Promise((resolve, reject) => {  
  setTimeout(() => resolve('abc'), 1000);  
});

promise.then((val) => {  
  console.log(val);  
})

When we run the code above, we should see abc logged. As we can see, the promise will supply the value when the promise is fulfilled with a call to the resolve function.

A promise has three states. It can be pending, which means that the promise has been fulfilled or rejected. It can be fulfilled, which means that the operation completed successfully. Or it can be rejected, which means that the promise operation failed.

A pending promise can either be fulfilled with a value or be rejected with some error. When a promise is fulfilled, then the corresponding resolve value is picked up by the then function and the callback function passed into the then function is called. If a promise is rejected, then we can optionally catch the error with the catch function, which also takes a callback function with the error. Both the then and catch functions return promises, so they can be chained together.

For example, we can write something like:

const promise = (num) => {  
  return new Promise((resolve, reject) => {  
    setTimeout(() => {  
      if (num === 1) {  
        resolve('resolved')  
      } else {  
        reject('rejected')  
      }  
    }, 1000);  
  });  
}

promise(1)  
  .then((val) => {  
    console.log(val);  
  })  
  .catch((error) => {  
    console.log(error);  
  })promise(2)  
  .then((val) => {  
    console.log(val);  
  })  
  .catch((error) => {  
    console.log(error);  
  })

In the code above, we have a function promise that returns a JavaScript promise that fulfills the promise with the value resolved when num is 1, and reject the promise with error rejected otherwise. So we run:

promise(1)  
  .then((val) => {  
    console.log(val);  
  })  
  .catch((error) => {  
    console.log(error);  
  })

Then the then function runs, the promise returned by the promise(1) function call is fulfilled since num is 1, and the resolved value is available set in val. So when we run console.log(val) we get resolved logged. When we run the code below:

promise(2)  
  .then((val) => {  
    console.log(val);  
  })  
  .catch((error) => {  
    console.log(error);  
  })

Then the catch function runs because the promise returned by the promise(2) function call is rejected, and the rejected error value is available set in error. So when we run console.log(error) we get rejected logged.

A JavaScript promise object has the following properties: length and prototype. The length always has its value set to 1 for the number of constructor arguments, which is always one. The prototype property represents the prototype of the promise object.

A promise also has the finally method to run code that’s run no matter whether the promise is fulfilled or rejected. The finally method takes a callback function as an argument, where any code that you want to run, regardless of the outcome of the promise, is run. For example, if we run:

Promise.reject('error')  
  .then((value) => {  
    console.log(value);  
  })  
  .catch((error) => {  
    console.log(error);  
  })  
  .finally(() => {  
    console.log('finally runs');  
  })

Then we get error and finally runs logged because the original promise was rejected with the reason error. Then whatever code in the callback of the finally method runs.

The main benefit of using promises for writing asynchronous code is that we can use them to run asynchronous code sequentially. To do this, we can chain promises with the then function. The then function takes a callback function that runs when the promise is fulfilled. It also takes a second argument when the promise is rejected. To chain promises, we have to return another promise as the return value of the first callback function of the then function. It can return other values, like nothing, if we don’t want to chain another promise to the existing promise. We can return a value, which will be resolved and the value retrievable in the next then function. It can also throw an error. Then the promise returned by then gets rejected with the thrown error as the value. It can also return an already fulfilled or rejected promise, which will get the fulfilled value when a then function is chained after it or get an error reason in the callback of the catch function.

For example, we can write the following:

Promise.resolve(1)  
  .then(val => {  
    console.log(val);  
    return Promise.resolve(2)  
  })  
  .then(val => {  
    console.log(val);  
  })Promise.resolve(1)  
  .then(val => {  
    console.log(val);  
    return Promise.reject('error')  
  })  
  .then(val => {  
    console.log(val);  
  })  
  .catch(error => console.log(error));Promise.resolve(1)  
  .then(val => {  
    console.log(val);  
    throw new Error('error');  
  })  
  .then(val => {  
    console.log(val);  
  })  
  .catch(error => console.log(error));

In the first example, we chained the promises and they all resolve to a value. All the promises were already resolved into values. In the second and last examples, we reject the second promise or throw an error. They both do the same thing. The second promise is rejected, and the error reason will be logged in the callback of the catch function. We can also chain pending promises, like in the following code:

const promise1 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(1), 1000);  
});

const promise2 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(2), 1000);  
});

promise1  
  .then(val => {  
    console.log(val);  
    return promise2;  
  })  
  .then(val => {  
    console.log(val);  
  })  
  .catch(error => console.log(error));

The callback of the first then function returned promise2, which is a pending promise.


Methods

JavaScript promises have the following methods.

Promise.all(iterable)

Promise.all takes an iterable object that lets us run multiple promises in parallel in some computers and serially in other computers. This is handy for running multiple promises that don’t depend on each other’s values. It takes in an iterable with a list of promises, usually an array, and then returns a single Promise that’s resolved when the promises that are in the iterable are resolved.

For example, we can write code like the following to run multiple promises with Promise.all:

const promise1 = Promise.resolve(1);  
const promise2 = 2;  
const promise3 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(3), 1000);  
});  
Promise.all([promise1, promise2, promise3])  
  .then((values) => {  
    console.log(values);  
  });

If we run the code above, then the console.log should log [1,2,3]. As we can see, it only returns a resolved value after all the promises are fulfilled. If some of them are rejected, then we won’t get any resolved value. Instead, we will get whatever error value is returned by the rejected promises. It will stop at the first rejected promise and send that value to the callback of the catch function. For example, if we have:

const promise1 = Promise.resolve(1);  
const promise2 = Promise.reject(2);  
const promise3 = new Promise((resolve, reject) => {  
  setTimeout(() => reject(3), 1000);  
});  
Promise.all([promise1, promise2, promise3])  
  .then((values) => {  
    console.log(values);  
  })  
  .catch(error => {  
    console.log(error);  
  });

Then we get 2 logged from the console.log in the callback of the catch function.

Promise.allSettled

Promise.allSettled returns a promise that resolves after all the given promises are resolved or rejected. It takes an iterable object with a collection of promises, for example, an array of promises. The resolved value of the returned promise is an array of the final status of each promise. For example, suppose we have:

const promise1 = Promise.resolve(1);  
const promise2 = Promise.reject(2);  
const promise3 = new Promise((resolve, reject) => {  
  setTimeout(() => reject(3), 1000);  
});  
Promise.allSettled([promise1, promise2, promise3])  
  .then((values) => {  
    console.log(values);  
  })

If we run the code above, then we get an array with three entries, with each entry being an object that has the status and value properties for the fulfilled promises and an object that has the status and reason properties for the rejected promises. For example, the code above will log {status: “fulfilled”, value: 1}, {status: “rejected”, reason: 2}, {status: “rejected”, reason: 3} . The fulfilled status is logged for the successful promises and therejected status for the rejected ones.

Promise.race

The Promise.race method returns a promise that resolves to the resolved value of the promise that’s fulfilled first. It takes an iterable object with a collection of promises, for example, an array of promises. If the iterable passed in is empty, then the returned promise will forever be pending. If the iterable object contains one or more non-promise values or already settled promises, then Promise.race will return to the first of these entries. For example, if we have:

const promise1 = Promise.resolve(1);  
const promise2 = Promise.resolve(2);  
const promise3 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(3), 1000);  
});  
Promise.race([promise1, promise2, promise3])  
  .then((values) => {  
    console.log(values);  
  })

Then we see that 1 is logged. That’s because promise1 is the first one that’s resolved since it’s resolved instantaneously before the next lines are run. Likewise, if we have a non-promise value in the array that we pass in as the argument, like in the following code:

const promise1 = 1;  
const promise2 = Promise.resolve(2);  
const promise3 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(3), 1000);  
});  
Promise.race([promise1, promise2, promise3])  
  .then((values) => {  
    console.log(values);  
  })

Then we get the same thing logged since it’s the non-promise value in the array that we pass into the Promise.race method. Synchronous code always runs before the asynchronous code, so it doesn’t matter where the synchronous code is. If we have:

const promise1 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(1), 2000);  
});

const promise2 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(2), 1000);  
});

const promise3 = 3;  
Promise.race([promise1, promise2, promise3])  
  .then((values) => {  
    console.log(values);  
  })

Then we get 3 logged since setTimeout puts the callback function in the queue to be run later, so it’s going to be run later than the synchronous code.

Finally, if we have:

const promise1 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(1), 2000);  
}); 
 
const promise2 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(2), 1000);  
});

Promise.race([promise1, promise2])  
  .then((values) => {  
    console.log(values);  
  })

Then we get 2 in the console log because a promise that’s resolved in one second is going to be resolved earlier than a promise resolved in two seconds.

Promise.reject

Promise.reject returns a promise that’s rejected with a reason. It’s useful to reject a promise with an object that’s an instance of Error. For example, if we have the following code:

Promise.reject(new Error('rejected'))  
  .then((value) => {  
    console.log(value);  
  })  
  .catch((error) => {  
    console.log(error);  
  })

Then we get rejected logged.

Promise.resolve

Promise.resolve returns a promise that’s resolved to the value that’s passed into the argument of the resolve function. We can also pass in an object with the then property where the value of it is the callback function of a promise. If the value has a then method, then the promise will be fulfilled with the value fulfilled by the then function. That is, the first parameter of the function for the value of the then function is the same as resolve, and the second parameter is the same as reject. For example, we can write the following:

Promise.resolve(1)  
  .then((value) => {  
    console.log(value);  
  })

Then we get 1 logged since 1 is the value we passed into the resolve function to return the promise with resolved value 1.

If we pass in an object with a then method inside, like in the following code:

Promise.resolve({  
    then(resolve, reject) {  
      resolve(1);  
    }  
  })  
  .then((value) => {  
    console.log(value);  
  })

Then we get value 1 logged. That’s because the Promise.resolve function will run the then function, and the resolve parameter for the function set to the then property will be assumed to be a function that’s called the resolve function in a promise. If we instead called the reject function inside the then function in the passed-in object, then we get a rejected promise, like in the following code:

Promise.resolve({  
    then(resolve, reject) {  
      reject('error');  
    }  
  })  
  .then((value) => {  
    console.log(value);  
  })  
  .catch((error) => {  
    console.log(error);  
  })

In the code above, we get error logged since the promise is rejected.


Async and Await

With async and await, we can shorten the promise code. Before async and await, we had to use the then function and we had to put callback functions as an argument of all of our then functions. This makes the code long as we have lots of promises. Instead, we can use the async and await syntax to replace the then function and its associated callbacks. For example, we can shorten the following code from:

const promise1 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(1), 2000);  
});

const promise2 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(2), 1000);  
});

promise1  
  .then((val1) => {  
    console.log(val1);  
    return promise2;  
  })  
  .then((val2) => {  
    console.log(val2);  
  })

to:

const promise1 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(1), 2000);  
});  

const promise2 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(2), 1000);  
});

(async () => {  
  const val1 = await promise1;  
  console.log(val1)  
  const val2 = await promise2;  
  console.log(val2)  
})()

We replaced the then and callbacks with await. Then we can assign the resolved values of each promise as variables. Note that if we use await for our promise code, then we have to put async in the function signature as we did in the above example. To catch errors, instead of chaining the catch function at the end, we use the catch clause. Also, instead of chaining the finally function at the bottom to run code when a promise ends, we use the finally clause after the catch clause.

For example, we can write:

const promise1 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(1), 2000);  
});  
const promise2 = new Promise((resolve, reject) => {  
  setTimeout(() => reject('error'), 1000);  
});

(async () => {  
  try {  
    const val1 = await promise1;  
    console.log(val1)  
    const val2 = await promise2;  
    console.log(val2)  
  } catch (error) {  
    console.log(error)  
  } finally {  
    console.log('finally runs');  
  }})()

In the code above, we got the resolved value of the promise assigned to a variable instead of getting the value in the callback of the then function, like in the const response = await promise1 line above. Also, we used the try...catch...finally blocks to catch the errors of rejected promises and the finally clause instead of the finally function with a callback passed in to create code that runs no matter what happens to the promises.

Like any other function that uses promises, async functions always return promises and cannot return anything else. In the example above, we showed that we can chain promises in a much shorter way than with the then function with callbacks passed in as an argument.


Conclusion

With promises, we can write asynchronous code with ease. Promises are objects that represent a process that runs for an indeterminate amount of time and may result in success or failure. To create promises in JavaScript, we use a Promise object that’s a constructor for creating promises.

The Promise constructor takes a function called the executor function that has the resolve and reject parameters. Both parameters are functions that we call to either fulfill the promise (which means end it successfully with some value) or to reject the promise (which means returning some error value and marking the promise as failed). The function’s return value is ignored. Therefore, promises can’t return anything other than promises.

Promises are chainable since promises return promises. The then function of promises can throw an error, return resolved values, or return other promises that are pending, fulfilled, or rejected.