
/g.exec(html)) !== null) {
positions.push(match.index);
}
If the html string contains at least one <p> tag, then it will loop forever. For each iteration of the loop, we’re creating a new RegExp object with `lastIndex` set to 0, so `exec()` always begins at the start of the string, and if there is a match, it will keep matching over and over. The solution, of course, is to define the RegExp once, and save it to a variable so that we’re using the same RegExp object for each iteration of the loop.
On the other hand, sometimes reusing a RegExp object is the wrong thing to do. Suppose, for example, that we want to loop through all of the words in a dictionary to find words that contain pairs of double letters.
let dictionary = [ "apple", "book", "coffee" ];
let doubleLetterWords = [];
let doubleLetter = /(\w)\1/g;
for(let word of dictionary) {
if (doubleLetter.test(word)) {
doubleLetterWords.push(word);
}
}
`doubleLetterWords`
\[“apple”, “coffee”\]: “book” is missing!
Because we set the g flag on the RegExp, the `lastIndex` property is changed after successful matches, and the `test()` method (which is based on `exec()`) starts searching for a match at the position specified by `lastIndex`. After matching the "pp" in "apple", `lastIndex` is 3, and so we start searching the word "book" at position 3 and do not see the "oo" that it contains.
### Dates and Times
let now = new Date();
The current time
let epoch = new Date(0);
Midnight, January 1st, 1970, GMT
let century = new Date(2100,
0,
1,
2, 3, 4, 5);
Year 2100
January
1st
02:03:04.005, local
let century = new Date(Date.UTC(2100, 0, 1));
Midnight in GMT, January 1, 2100
If you print a date (with console.log(century), for example), it will, by default, be printed in your local time zone. If you want to display a date in UTC, you should explicitly convert it to a string with `toUTCString()` or` toISOString()`.
if you pass a string to the Date() constructor, it will attempt to parse that string as a date and time specification
`let century = new Date("2100-01-01T00:00:00Z");`
Once you have a Date object, various get and set methods allow you to query and modify the year, month, day-of-month, hour, minute, second, and millisecond fields of the Date. Each of these methods hastwo forms: one that gets or sets using local time and one that gets or sets using UTC time.
Note that the methods for querying the day-of-month are `getDate()` and `getUTCDate()`. The more natural-sounding functions `getDay()` and `getUTCDay()` return the day-of-week (0 for Sunday through 6 for Saturday). The day-of-week is read-only, so there is not a corresponding `setDay()` method.
### Timestamps
JavaScript represents dates internally as integers that specify the number of milliseconds since (or before) midnight on January 1, 1970, UTC time.
For any Date object, the `getTime()` method returns this internal value, and the `setTime()` method sets it.
d.setTime(d.getTime() + 30000);
add 30 secs
The static Date.now() method returns the current time as a timestamp and is helpful when you want to measure how long your code takes to run
let startTime = Date.now();
reticulateSplines(); // Do some time-consuming operation
let endTime = Date.now();
console.log(`Spline reticulation took ${endTime -startTime}ms.`);
adds three months and two weeks to the current date:
`let d = new Date();`
`d.setMonth(d.getMonth() + 3, d.getDate() + 14);`
### Formatting and Parsing Date Strings
let d = new Date(2020, 0, 1, 17, 10, 30);
d.toString()
“Wed Jan 01 2020 17:10:30 GMT-0800 (Pacific Standard Time)”
d.toUTCString()
“Thu, 02 Jan 2020 01:10:30 GMT”
d.toLocaleDateString()
“1/1/2020”: ‘en-US’ locale
d.toLocaleTimeString()
“5:10:30 PM”: ‘en-US’ locale
d.toISOString()
“2020–01–02T01:10:30.000Z”
there is also a static Date.parse() method that takes a string as its argument, attempts to parse it as a date and time, and returns a timestamp representing that date. `Date.parse()` is able to parse the same strings that the `Date()` constructor can and is guaranteed to be able to parse the output of` toISOString(), toUTCString()`, and` toString()`.
### Error Classes
One good reason to use an Error object is that, when you create an Error, it captures the state of the JavaScript stack, and if the exception is uncaught, the stack trace will be displayed with the error message, which will help you debug the issue.
Error objects have two properties: `message` and `name`, and a` toString()` method. Node and all modern browsers also define a `stack` property on Error objects.
Subclasses are `EvalError, RangeError, ReferenceError, SyntaxError, TypeError,` and `URIError`.
You should feel free to define your own Error subclasses that best encapsulate the error conditions of your own program.
class HTTPError extends Error {
constructor(status, statusText, url) {
super(`${status} ${statusText}: ${url}`);
this.status = status;
this.statusText = statusText;
this.url = url;
}
get name() { return "HTTPError"; }
}
let error = new HTTPError(404, "Not Found", "http://example.com/");
error.status
404
error.message
“404 Not Found:http://example.com/"
error.name
HTTPError
### JSON Serialization and Parsing
JavaScript supports JSON serialization and deserialization with the two functions `JSON.stringify()` and `JSON.parse().`
let o = {s: "", n: 0, a: [true, false, null]};
let s = JSON.stringify(o);
s == ‘{“s”:””,”n”:0,”a”:\[true,false,null\]}’
let copy = JSON.parse(s);
copy == {s: “”, n: 0, a:\[true, false, null\]}
Inefficient way of creating a deep copy of an object
function deepcopy(o) {
return JSON.parse(JSON.stringify(o));
}
Typically, you pass only a single argument to `JSON.stringify()` and `JSON.parse()`. Both functions accept an optional second argument that allows us to extend the JSON format.
`JSON.stringify()` also takes an optional third argument. If you would like your JSONformatted string to be human-readable (if it is being used as a configuration file, for example), then you should pass null as the second argument and pass a number or string as the third argument. If the third argument is a number, then it will use that number of spaces for each indentation level. If the third argument is a string of whitespace (such as '\\t'), it will use that string for each level of indent.
### JSON Customizations
If `JSON.stringify()` is asked to serialize a value that is not natively supported by the JSON format, it looks to see if that value has a `toJSON()` method, and if so, it calls that method and then stringifies the return value in place of the original value. Date objects implement `toJSON()`: it returns the same string that `toISOString()` method does.
If you need to re-create Date objects (or modify the parsed object inany other way), you can pass a “reviver” function as the second argument to `JSON.parse()`.
let data = JSON.parse(text, function(key, value) {
if (key[0] === "_") return undefined;
if (typeof value === "string" && /^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d.\d\d\dZ$/.test(value)) {
return new Date(value);
}
return value;
});
### The Console API
Console functions that print their arguments like console.log() have a little-known feature: if the first argument is a string that includes `%s, %i, %d, %f, %o, %O, or %c`, then this first argument is treated as format string, and the values of subsequent arguments are substituted into the string in place of the two-character % sequences.
### URL API
let url = new URL("https://example.com:8000/path/name?q=term#fragment");
url.href
“https://example.com:8000/path/name?q=term\#fragment"
url.origin
“https://example.com:8000"
url.protocol
“https:”
url.host
“example.com:8000”
url.hostname
“example.com”
url.port
“8000”
url.pathname
“/path/name”
url.search
“?q=term”
url.hash
“\#fragment”
let url = new URL("https://example.com");
url.pathname = "api/search";
Add a path to an API endpoint
url.search = "q=test";
Add a query parameter
url.toString()
“https://example.com/api/search?q=test"
One of the important features of the URL class is that it correctly adds punctuation and escapes special characters in URLs when that is needed
let url = new URL("https://example.com");
url.pathname = "path with spaces";
url.pathname
“/path%20with%20spaces”
url.search = "q=foo#bar";
url.search
“?q=foo%23bar”
url.href
“https://example.com/path%20with%20spaces?q=foo%23bar"
Often, however, HTTP requests encode the values of multiple form fields or multiple API parameters into the query portion of a URL. In this format, the query portion of the URL is a question mark followed by one or more name/value pairs, which are separated from one another by ampersands.
If you want to encode these kinds of name/value pairs into the query portion of a URL, then the searchParams property will be more useful than the search property.
`let url = new URL("https://example.com/search");`
`url.search`
“”
`url.searchParams.append("q", "term");`
`url.search`
“?q=term”
`url.searchParams.set("q", "x");`
`url.search`
“?q=x”
`url.searchParams.append("opts", "1");`
`url.search`
“?q=x&opts=1”
The value of the searchParams property is a URLSearchParams object.
`let url = new URL("http://example.com");`
`let params = new URLSearchParams();`
`params.append("q", "term");`
`params.append("opts", "exact");`
`params.toString()`
“q=term&opts=exact”
`url.search = params;`
`url.href`
“http://example.com/?q=term&opts=exact"
### Timers
`setTimeout()` and` setInterval()`—that allow programs to ask the browser to invoke a function after a specified amount of time has elapsed or to invoke the function repeatedly at a specified interval.
setTimeout(() => { console.log("Ready..."); }, 1000);
setTimeout(() => { console.log("set..."); }, 2000);
setTimeout(() => { console.log("go!"); }, 3000);
If you want to invoke a function repeatedly, use `setInterval()`
Both `setTimeout()` and `setInterval()` return a value. If you save this value in a variable, you can then use it later to cancel the execution of the function by passing it to `clearTimeout()` or `clearInterval()`.
let clock = setInterval(() => {
console.clear();
console.log(new Date().toLocaleTimeString());
}, 1000);
setTimeout(() => { clearInterval(clock); }, 10000);
After 10 seconds: stop the repeating code above
### Iterators and Generators
The iterator method of an iterable object does not have a conventional name but uses the Symbol, Symbol.iterator as its name. So a simple for/of loop over an iterable object iterable could also be written the hard way, like this:
let iterable = [99];
let iterator = iterable[Symbol.iterator]();
for(let result = iterator.next(); !result.done; result =iterator.next()) {
console.log(result.value) // result.value == 99
}
When you want to iterate though a “partially used” iterator:
`let list = [1,2,3,4,5];`
`let iter = list[Symbol.iterator]();`
`let head = iter.next().value;`
head == 1
`let tail = [...iter];`
tail == \[2,3,4,5\]
### Implementing Iterable Objects
we will implement the Range class one more time, making it iterable without relying on a generator.
In order to make a class iterable, you must implement a method whose name is the Symbol `Symbol.iterator`
class Range {
constructor (from, to) {
this.from = from;
this.to = to;
}
has(x) { return typeof x === "number" && this.from <= x && x <= this.to; }
toString() { return `{ x | ${this.from} ≤ x ≤ ${this.to}}`; }
[Symbol.iterator]() {
let next = Math.ceil(this.from);
let last = this.to;
return {
next() {
return (next <= last) ? { value: next++ } : { done: true };
},
[Symbol.iterator]() { return this; }
};
}
}
for(let x of new Range(1,10)) console.log(x);
Logs numbers 1 to 10
[...new Range(-2,2)]
\[-2, -1, 0,1, 2\]
In addition to making your classes iterable, it can be quite useful to define functions that return iterable values.
Return an iterable object that iterates the result of applying `f()` to each value from the source iterable
function map(iterable, f) {
let iterator = iterable[Symbol.iterator]();
return {
[Symbol.iterator]() { return this; },
next() {
let v = iterator.next();
if (v.done) {
return v;
}
else {
return { value: f(v.value) };
}
}
};
}
`[...map(new Range(1,4), x => x*x)]`
\[1, 4, 9, 16\]
Return an iterable object that filters the specified iterable, iterating only those elements for which the predicate returns true
function filter(iterable, predicate) {
let iterator = iterable[Symbol.iterator]();
return {
[Symbol.iterator]() { return this; },
next() {
for(;;) {
let v = iterator.next();
if (v.done || predicate(v.value)) {
return v;
}
}
}
};
}
`[...filter(new Range(1,10), x => x % 2 === 0)]`
\[2,4,6,8,10\]
### Generators
Particularly useful when the values to be iterated are not the elements of a data structure, but the result of a computation.
To create a generator, you must first define a generator function — defined with the keyword `function*` rather than `function`
When you invoke a generator function, it does not actually execute the function body, but instead returns a generator object. This generator object is an iterator.
Calling its `next()` method causes the body of the generator function to run from the start (or whatever its current position is) until it reaches a `yield` statement.
The value of the `yield` statement becomes the value returned by the `next()` call on the iterator.
function* oneDigitPrimes() {
yield 2;
yield 3;
yield 5;
yield 7;
}
let primes = oneDigitPrimes();
we get a generator
primes.next().value
2
primes.next().value
3
primes.next().value
5
primes.next().value
7
primes.next().done
true
Generators have a `Symbol.iterator` method to make them iterable
`primes[Symbol.iterator]()`
`[...oneDigitPrimes()]`
\[2,3,5,7\]
let sum = 0;
for(let prime of oneDigitPrimes()) sum += prime;
sum
17
Like regular functions, however, we can also define generators in expression form.
const seq = function*(from,to) {
for(let i = from; i <= to; i++) yield i;
};
[...seq(3,5)]
\[3, 4, 5\]
In classes and object literals, we can use shorthand notation to omit the function keyword entirely when we define methods.
let o = {
x: 1, y: 2, z: 3,
*g() {
for(let key of Object.keys(this)) {
yield key;
}
}
};
`[...o.g()]`
\[“x”, “y”, “z”, “g”\]
Generators often make it particularly easy to define iterable classes.
*[Symbol.iterator]() {
for(let x = Math.ceil(this.from); x <= this.to; x++)
yield x;
}
### Generator Examples
Generators are more interesting if they actually generate the values they yield by doing some kind of computation.
generator function that yields Fibonacci numbers
function* fibonacciSequence() {
let x = 0, y = 1;
for(;;) {
yield y;
[x, y] = [y, x+y];
}
}
If this generator is used with the … spread operator, it will loop until memory is exhausted and the program crashes.
Use it in a `for/of` loop, however
function fibonacci(n) {
for(let f of fibonacciSequence()) {
if (n-- <= 0) return f;
}
}
fibonacci(20)
10946
This kind of infinite generator becomes more useful with a `take()` generator like this
function* take(n, iterable) {
let it = iterable[Symbol.iterator]();
while(n-- > 0) {
let next = it.next();
if (next.done) return;
else yield next.value;
}
}
[...take(5, fibonacciSequence())]
\[1, 1, 2, 3, 5\]
### Asynchronous Javascript
`Promises`, new in ES6, are objects that represent the not-yet-available result of an asynchronous operation.
The keywords `async` and `await` were introduced in ES2017 and provide new syntax that simplifies asynchronous programming by allowing you to structure your Promise based code as if it was synchronous.
Asynchronous iterators and the` for/await` loop were introduced in ES2018 and allow you to work with streams of asynchronous events using simple loops that appear synchronous.
### Asynchronous Programming with Callbacks
### Timers
`setTimeout(checkForUpdates, 60000);`
let updateIntervalId = setInterval(checkForUpdates, 60000);
function stopCheckingForUpdates() {
clearInterval(updateIntervalId);
}
### Events
Event-driven JavaScript programs register callback functions for specified types of events in specified contexts, and the web browser invokes those functions whenever the specified events occur.
These callback functions are called event handlers or event listeners, and they are registered with `addEventListener()`
Ask the web browser to return an object representing the HTML <button> element that matches this CSS selector:
`let okay = document.querySelector('#confirmUpdateDialogbutton.okay');`
Now register a callback function to be invoked when the user clicks on that button
`okay.addEventListener('click', applyUpdate);`
### Network Events
JavaScript running in the browser can fetch data from a web server with code like this:
function getCurrentVersionNumber(versionCallback) {
let request = new XMLHttpRequest();
request.open("GET", "http://www.example.com/api/version");
request.send();
request.onload = function() {
if (request.status === 200) {
let currentVersion = parseFloat(request.responseText);
versionCallback(null, currentVersion);
}
else {
versionCallback(response.statusText, null);
}
};
request.onerror = request.ontimeout = function(e) {
versionCallback(e.type, null);
};
}
### Promises
Promises, a core language feature designed to simplify asynchronous programming.
A Promise is an object that represents the result of an asynchronous computation. That result may or may not be ready yet, and the Promise API is intentionally vague about this: there is no way to synchronously get the value of a Promise; you can only ask the Promise to call a callback function when the value is ready.
One real problem with callback-based asynchronous programming is that it is common to end up with callbacks inside callbacks inside callbacks, with lines of code so highly indented that it is difficult to read.
Promises allow this kind of nested callback to be re-expressed as a more linear Promise chain that tends to be easier to read and easier to reason about.
Another problem with callbacks is that they can make handling errors difficult. If an asynchronous function (or an asynchronously invoked callback) throws an exception, there is no way for that exception to propagate back to the initiator of the asynchronous operation. This is a fundamental fact about asynchronous programming: it breaks exception handling. Promises help here by standardizing a way to handle errors and providing a way for errors to propagate correctly through a chain of promises.
Note that Promises represent the future results of single asynchronous computations. They cannot be used to represent repeated asynchronous computations, however.
We can’t use Promises to replace `setInterval()` because that function invokes a callback function repeatedly, which is something that Promises are just not designed to do.
### Using Promises
How we would use this Promise returning utility function
getJSON(url).then(jsonData => {
// callback function that will be asynchronously invoked with the parsed JSON value when it becomes available.
});
The Promise object defines a then() instance method. Instead of passing our callback function directly to `getJSON()`, we instead pass it to the then() method. When the HTTP response arrives, the body of that response is parsed as JSON, and the resulting parsed value is passed to the function that we passed to `then()`.
If you call the `then()` method of a Promise object multiple times, each of the functions you specify will be called when the promised computation is complete.
Unlike many event listeners, though, a Promise represents a single computation, and each function registered with then() will be invoked only once.
function displayUserProfile(profile) { ...}
getJSON("/api/user/profile").then(displayUserProfile);
### HANDLING ERRORS WITH PROMISES
Asynchronous operations, particularly those that involve networking, can typically fail in a number of ways, and robust code has to be written to handle the errors that will inevitably occur.
`getJSON("/api/user/profile").then(displayUserProfile, handleProfileError);`
if `getJSON()` runs normally, it passes its result to `displayUserProfile()`. If there is an error (the user is not logged in, the server is down, the user’s internet connection dropped, the request timed out, etc.), then `getJSON()` passes an Error object to `handleProfileError()`.
In practice, it is rare to see two functions passed to then(). There is a better and more idiomatic way of handling errors when working with Promises.
To understand it, first consider what happens if `getJSON()` completes normally but an error occurs in `displayUserProfile()`. That callback function is invoked asynchronously when `getJSON()` returns, so it is also asynchronous and cannot meaningfully throw an exception (because there is no code on the call stack to handle it).
getJSON("/api/user/profile").then(displayUserProfile).catch(handleProfileError);
With this code, a normal result from` getJSON()` is still passed to `displayUserProfile()`, but any error in `getJSON()` or in `displayUserProfile()` (including any exceptions thrown by `displayUserProfile`) get passed to `handleProfileError()`.
### Chaining Promises
One of the most important benefits of Promises is that they provide a natural way to express a sequence of asynchronous operations as a linear chain of `then()` method invocations, without having to nest each operation within the callback of the previous one.
fetch(documentURL)
.then(response => response.json())
.then(document => {return render(document); })
.then(rendered => {cacheInDatabase(rendered); })
.catch(error => handle(error));
has largely been replaced by the newer, Promise-based Fetch API. In its simplest form, this new HTTP API is just the function fetch(). That promise is fulfilled when the HTTP response begins to arrive and the HTTP status and headers are available.
fetch("/api/user/profile")
.then(response => {
if (response.ok && response.headers.get("Content-Type") === "application/json") {
// What can we do here? We don't actually have the response body yet.
}
});
But although the initial Promise is fulfilled, the body of the response may not yet have arrived. So these `text()` and` json()` methods for accessing the body of the response themselves return Promises.
fetch("/api/user/profile")
.then(response => {
return response.json();
})
.then(profile => {
displayUserProfile(profile);
});
There is a second `then()` in the chain, which means that the first invocation of the `then()` method must itself return a Promise. That is not how Promises work, however.
When we write a chain of `.then()` invocations, we are not registering multiple callbacks on a single Promise object. Instead, each invocation of the `then()` method returns a new Promise object. That new Promise object is not fulfilled until the function passed to `then()` is complete.
fetch(theURL) // task 1; returns promise 1
.then(callback1) // task 2; returns promise 2
.then(callback2); // task 3; returns promise 3
### Resolving Promises
There is actually a fourth Promise object involved as which brings up the point of what it means for a Promise to be “resolved.”
fetch() returns a Promise object which, when fulfilled, passes a Response object to the callback function we register. This Response object has `.text(), .json(),` and other methods to request the body of the HTTP response in various forms. But since the body may not yet have arrived, these methods must return Promise objects.
“task 2” calls the `.json()` method and returns its value. This is the fourth Promise object, and it is the return value of the `callback1()` function.
Let’s consider:
function c1(response) {
let p4 = response.json();
return p4;
}
// callback 1
// returns promise 4
function c2(profile) {
displayUserProfile(profile);
}
// callback 2
let p1 = fetch("/api/user/profile");
promise 1, task 1
let p2 = p1.then(c1);
promise 2, task 2
let p3 = p2.then(c2);
promise 3, task 3
In order for Promise chains to work usefully, the output of task 2 must become the input to task 3. The input to task 3 is the body of the URL that was fetched, parsed as a JSON object. But the return value of callback c1 is not a JSON object, but Promise p4 for that JSON object.
When p1 is fulfilled, c1 is invoked, and task 2 begins. And when p2 is fulfilled, c2 is invoked, and task 3 begins.
And when p2 is fulfilled, c2 is invoked, and task 3 begins. But just because task 2 begins when c1 is invoked,it does not mean that task 2 must end when c1 returns.
Promises are about managing asynchronous tasks, and if task 2 is asynchronous, then that task will not be complete by the time the callback returns.
When you pass a callback c to the `then()` method, `then()` returns a Promise p and arranges to asynchronously invoke c at some later time. The callback performs some computation and returns a value v. When the callback returns, p is resolved with the value v. When a Promise is resolved with a value that is not itself a Promise, it is immediately fulfilled with that value.
So if c returns a non-Promise, that return value becomes the value of p, p is fulfilled and we are done. But if the return value v is itself a Promise, then p is resolved but not yet fulfilled.
At this stage, p cannot settle until the Promise v settles. If v is fulfilled, then p will be fulfilled to the same value. If v is rejected, then p will be rejected for the same reason. This is what the “resolved” state of a Promise means
the Promise has become associated with, or “locked onto,” another Promise. We don’t know yet whether p will be fulfilled or rejected, but our callback c no longer has any control over that. p is “resolved” in the sense that its fate now depends entirely on what happens to Promise v.
Let’s bring this back to our URL-fetching example. When c1 returns p4, p2 is resolved. But being resolved is not the same as being fulfilled, so task 3 does not begin yet. When the full body of the HTTP response becomes available, then the .`json()` method can parse it and use that parsed value to fulfill p4. When p4 is fulfilled, p2 is automatically fulfilled as well, with the same parsed JSON value. At this point, the parsed JSON object is passed to c2, and task 3 begins.
### More on Promises and Errors
With synchronous code, if you leave out error-handling code, you’ll at least get an exception and a stack trace that you can use to figure out what is going wrong. With asynchronous code, unhandled exceptions will often go unreported, and errors can occur silently, making them much harder to debug. The good news is that the `.catch()` method makes it easy to handle errors when working with Promises.
THE CATCH AND FINALLY METHODS
The `.catch()` method of a Promise is simply a shorthand way to call `.then()` with null as the first argument and an error-handling callback as the second argument.
Normal exceptions don’t work with asynchronous code. The `.catch()` method of Promises is an alternative that does work for asynchronous code.
fetch("/api/user/profile")
.then(response => {
if (!response.ok) {
return null;
}
let type = response.headers.get("content-type");
if (type !== "application/json") {
throw new TypeError(`Expected JSON, got ${type}`);
}
return response.json();
})
.then(profile => {
if (profile) {
displayUserProfile(profile);
}
else {
displayLoggedOutProfilePage();
}
})
.catch(e => {
if (e instanceof NetworkError) {
displayErrorMessage("Check your internet connection.");
}
else if (e instanceof TypeError) {
displayErrorMessage("Something is wrong with our server!");
}
else {
console.error(e);
}
});
p1 is the Promise returned by the `fetch()` call
p2 is the Promise returned by the first `.then()` call
c1 is the callback that we pass to that .`then()` call
p3 is the Promise returned by the second `.then()` call
c2 is the callback we pass to that call
c3 is the callback that we pass to the `.catch()` call
The first thing that could fail is the fetch() request itself. Let’s say p1 was rejected with a NetworkError object.
We didn’t pass an error-handling callback function as the second argument to the `.then()` call, so p2 rejects as well with the same NetworkError object.
Without a handler, though, p2 is rejected, and then p3 is rejected for the same reason.
At this point, the c3 error-handling callback is called, and the NetworkError-specific code within it runs.
There are a couple of things worth noting about this code. First, notice that the error object thrown with a regular, synchronous throw statement ends up being handled asynchronously with a `.catch()` method invocation in a Promise chain. This should make it clear why this shorthand method is preferred over passing a second argument to .`then()`, and also why it is so idiomatic to end Promise chains with a `.catch()` call.
it is also perfectly valid to use `.catch()` elsewhere in a Promise chain. If one of the stages in your Promise chain can fail with an error, and if the error is some kind of recoverable error that should not stop the rest of the chain from running, then you can insert a `.catch()` call in the chain, resulting in code that might look like this:
startAsyncOperation()
.then(doStageTwo)
.catch(recoverFromStageTwoError)
.then(doStageThree)
.then(doStageFour)
.catch(logStageThreeAndFourErrors);
If the callback returns normally, then the `.catch()` callback will be skipped, and the return value of the previous callback will become the input to the next .`then()` callback.
Once an error has been passed to a `.catch()` callback, it stops propagating down the Promise chain. A `.catch()` callback can throw a new error, but if it returns normally, than that return value is used to resolve and/or fulfill the associated Promise, and
the error stops propagating.
Sometimes, in complex network environments, errors can occur more or less at random, and it can be appropriate to handle those errors by simply retrying the asynchronous request.
queryDatabase()
.catch(e => wait(500).then(queryDatabase))
.then(displayTable)
.catch(displayDatabaseError);
### Promises in Parallel
Sometimes,we want to execute a number of asynchronous operations in parallel. The function `Promise.all()` can do this. `Promise.all()` takes an array of Promise objects as its input and returns a Promise.
The returned Promise will be rejected if any of the input Promises are rejected. Otherwise, it will be fulfilled with an array of the fulfillment values of each of the input Promises.
const urls = [ /* zero or more URLs here */ ];
promises = urls.map(url => fetch(url).then(r => r.text()));
Promise.all(promises)
.then(bodies => { /* do something with the array of strings */ })
.catch(e => console.error(e));
The Promise returned by `Promise.all()` rejects when any of the input Promises is rejected. This happens immediately upon the first rejection and can happen while other input Promises are still pending. In ES2020, `Promise.allSettled()` takes an array of input
Promises and returns a Promise, just like Promise.all() does. But `Promise.allSettled()` never rejects the returned Promise, and it does not fulfill that Promise until all of the input Promises have settled. The Promise resolves to an array of objects, with one object for each input Promise. Each of these returned objects has a status property set to "fulfilled" or "rejected." If the status is "fulfilled", then the object will also have a value property that gives the fulfillment value. And if the status is "rejected", then the object will also have a reason property that gives the error or rejection value of the corresponding Promise.
Promise.allSettled([Promise.resolve(1), Promise.reject(2),3]).then(results => {
results[0] // => { status: "fulfilled", value: 1 }
results[1] // => { status: "rejected", reason: 2 }
results[2] // => { status: "fulfilled", value: 3 }
});
Occasionally, you may want to run a number of Promises at once but may only care about the value of the first one to fulfill. In that case, you can use `Promise.race()` instead of `Promise.all()`. It returns a Promise that is fulfilled or rejected when the first of the Promises in the input array is fulfilled or rejected.
### Making Promises
### Promises in Sequence
### async and await
These new keywords dramatically simplify the use of Promises and allow us to write Promise-based, asynchronous code that looks like synchronous code that blocks while waiting for network responses or other asynchronous events.
Asynchronous code can’t return a value or throw an exception the way that regular synchronous code can. And this is why Promises are designed the way the are. The value of a fulfilled Promise is like the return value of a synchronous function. And the value of a rejected Promise is like a value thrown by a synchronous function.
`async` and `await` take efficient, Promise-based code and hide the Promises so that your asynchronous code can be as easy to read and as easy to reason about as inefficient, blocking, synchronous code.
Given a Promise object p, the expression await p waits until p settles. If p fulfills, then the value of await p is the fulfillment value of p. On the other hand, if p is rejected, then the await p expression throws the rejection value of p.
let response = await fetch("/api/user/profile");
let profile = await response.json();
It is critical to understand right away that the `await` keyword does not cause your program to block and literally do nothing until the specified Promise settles. The code remains asynchronous, and the `await` simply disguises this fact. This means that any code that uses await is itself asynchronous.
### async Functions
Because any code that uses await is asynchronous, there is one critical rule: you can only use the await keyword within functions that have been declared with the `async` keyword.
async function getHighScore() {
let response = await fetch("/api/user/profile");
let profile = await response.json();
return profile.highScore;
}
Declaring a function `async` means that the return value of the function will be a Promise even if no Promise-related code appears in the body of the function.
The `getHighScore()` function is declared `async`, so it returns a Promise. And because it returns a Promise, we can use the `await` keyword with it:
displayHighScore(await getHighScore());
### Awaiting Multiple Promises
Suppose that we’ve written our `getJSON()` function using async:
async function getJSON(url) {
let response = await fetch(url);
let body = await response.json();
return body;
}
And now suppose that we want to fetch two JSON values with this function
let value1 = await getJSON(url1);
let value2 = await getJSON(url2);
The problem with this code is that it is unnecessarily sequential: the fetch of the second URL will not begin until the first fetch is complete. If the second URL does not depend on the value obtained from the firstURL, then we should probably try to fetch the two values at the same time.
let [value1, value2] = await Promise.all([getJSON(url1), getJSON(url2)]);
### The for/await Loop
Suppose you have an array of URLs:
const urls = [url1, url2, url3];
You can call fetch() on each URL to get an array of Promises:
const promises = urls.map(url => fetch(url));
We could now use` Promise.all()` to wait for all the Promises in the array to be fulfilled. But suppose we want the results of the first fetch as soon as they become available and don’t want to wait for all the URLs to be fetched.
for(const promise of promises) {
response = await promise;
handle(response);
}
←>
for await (const response of promises) {
handle(response);
}
both examples will only work if they are within functions declared async; a `for/await` loop is no different than a regular await expression in that way
#### If you found this guide helpful feel free to checkout my GitHub/gist’s where I host similar content:
bgoonz’s gists · GitHub
bgoonz — Overview
Web Developer, Electrical Engineer JavaScript | CSS | Bootstrap | Python | React | Node.js | Express | Sequelize…github.com
Or checkout my personal resource site:
a/A-Student-Resources
Edit descriptiongoofy-euclid-1cd736.netlify.app
By Bryan Guner on [March 8, 2021](https://medium.com/p/64306cd6b0db).
Canonical link
Exported from [Medium](https://medium.com) on August 31, 2021.