JavaScript clipboard access

Accessing the clipboard with old document.execCommand had been superseded by the new Clipboard API. The latter API provides the ability to read from and write to the system clipboard asynchronously. But it is a relatively recent addition to the web standards and may not be fully supported in all browsers, check the browser compatibility before using.

Unlimited clipboard access can be dangerous. Especially reading from it may stole the user’s sensitive data like password. Writing may also bring malicious content to the clipboard that you may paste it somewhere unexpectedly. Therefore there are some restrictions for the methods used to access the clipboard, and reading is more strict.

At the end of the article, we give an example to write an given text to the clipboard. The example tries to work on most browsers.

Deprecated document.execCommand() for clipboard access

Note the document.execCommand() method is no longer recommended and may not be supported in some browsers. But it is still useful in some cases, such as on browsers that have not implemented the Async Clipboard API or not fully implemented.

The document.execCommand() can be used to execute commands like cut, copy and paste to interact with the clipboard. Its effect is analogous to that of a user executing such actions by pressing the keyboard.

Access clipboard with document.execCommand()

An example to execute paste ,copy and cut commands.

HTML

<textarea id="text-a">A</textarea>
<textarea id="text-b">B</textarea>

<button id="copy-button">Copy</button>
<button id="cut-button">Cut</button>
<button id="paste-button">Paste</button>

JavaScript

// Copy content of fromElem or the current selection to the clipboard.
function copy(fromElem = null) {
    if (fromElem) {
        fromElem.select(); // Make its content selected.
    }
    document.execCommand("copy");
}

// Remove content of fromElem or the current selectionand 
// and copy it to the clipboard. 
function cut(fromElem = null) {
    if (fromElem) {
        fromElem.select(); // Make its content selected.
    }
    document.execCommand("cut");
}

// Paste content from the clipboard to the "toElem" elment.
function paste(toElem) {
    toElem.focus(); // Make it focused.
    // Or use document.execCommand("paste", false, "Some given text");
    document.execCommand("paste");
}

const textA = document.querySelector("#text-a");
const textB = document.querySelector("#text-b");
document.querySelector("#copy-button")
    .addEventListener("click", () => copy(textA));
document.querySelector("#cut-button")
    .addEventListener("click", () => cut(textA));
document.querySelector("#paste-button")
    .addEventListener("click", () => paste(textB));

In the above example, all document.execCommand() calls are inside user-generated event handlers. If "cut" or "copy" is called outside such a handler, it may not work. On Chrome (v104), it just do not work. On Firefox, you may see the following warning:

document.execCommand(‘cut’/‘copy’) was denied because it was not
 called from inside a short running user-generated event handler.

And for "paste", even inside a user-generated event handler, it may be blocked for security.

Other commands

document.execCommand() can also be used to execute commands that the Clipboard API does not have. Like the insertText command which enables you to insert the given plain text at the insertion point programmatically.

Issues with document.execCommand()

  • It is synchronous. If the operation takes long time, it blocks the page.
  • There seems not a clear permission specification for clipboard access with document.execCommand().

The Clipboard API

Besides in a asynchronous way, with the Clipboard API, you can write and read arbitrary data to and from the clipboard, like text, rich text, HTML, images, etc. And it is convenient to copy a given data instead of the current selection to the clipboard.

Access clipboard with the Async Clipboard API

An example to read text and write text from and to the clipboard

HTML

<button id="copy-async-button">Copy async</button>
<button id="paste-async-button">Paste async</button>

JavaScript

function copyAsync(text) {
    if (navigator.clipboard) {
        // It will fail if the codument is not focused: "DOMException: Document is not focused."
        // In this case, click the page to make it focused.
        navigator.clipboard.writeText(text)
            .then(
                () =&gt; console.log("Copy async successful:", text),
                (err) =&gt; console.error("Copy async failed:", err)
            );
    }
}

function pasteAsync() {
    if (navigator.clipboard) {
        // It may fail if the `clipboard-read` permission is denied
        // to the current contenxt.
        // Firefox (at least v99) does not support
        // navigator.clipboard.readText()
        // or navigator.clipboard.read().
        navigator.clipboard.readText()
            .then(
                text =&gt; console.log("Paste async successful:", text),
                err =&gt; console.error("Paste async failed:", err)
            );
    }
}

document.querySelector("#copy-async-button")
    .addEventListener("click", () =&gt; copyAsync("Some text"));
document.querySelector("#paste-async-button")
    .addEventListener("click", pasteAsync);

Note the effect of the example vary depending on the browser and the context (like http or https ). See more in the Browser compatibility section.

The Clipboard API works only when the document is focused, otherwise you will get an exception in the console:

DOMException: Document is not focused.

In this case, click the page to make the document focused.

Permissions

Writing to the clipboard with the Clipboard API needs clipboard-write permission which is defined in Permissions API. This permission is granted automatically to pages when they are in the active tab.

Reading from the clipboard with the Clipboard API needs clipboard-read permission which must be requested. On some browsers of new versions, if the corresponding permission is not granted when reading from or writing to the clipboard, the browsers automatically prompt the user to request the permission.

You can also query whether a permission has been granted to the current context before using the API (clipboard-write and clipboard-read permission names are not supported in Firefox, at least not for v99).

navigator.permissions.query({name: "clipboard-read"})
    .then(result => {
        // state can be one of "granted", "prompted", "denied"
        if (result.state === "granted") { /* ... */}
    });

If the clipboard-read is denied, you will see below exception in the console if you click the paste button in the example.

DOMException: Read permission denied.

Browser compatibility

Check the full Browser compatibility.

Firefox

In the above example, we check the availability of the Clipboard API with navigator.clipboard before using them.

However, that is not enough. Firefox (v99) supports navigator.clibpard.writeText() but not navigator.clibpard.readText(). You need to put the call inside a try/catch block to work with most browsers.

And the browser does not support permission names of clipboard-write and clipboard-read. You will get an exception like below when you query it with Permission API:

Uncaught (in promise) TypeError: 'clipboard-read' (value of 'name'
 member of PermissionDescriptor) is not a valid value for enumeration
 PermissionName.

Chrome

Chrome (v104) does not prompt the user for permission when a permission like clibpard-read is denied. It just fails with an exception.

Safari

Safari use the WebKit browser engine. WebKit supports four MIME type representations: "text/plain""text/html""text/uri-list", and "image/png".

The Clipboard API is not supported on Safari until Safari V13.1 (year of 2020) and Safari iOS V13.4. You need special process for navigator.clibboard.write() on Safari. Below is an example to copy image data to the clibboard.

An example to copy an image for Safari and other browsers

Clipboard.write() writes arbitrary datato the clipboard., such as images,

HTML

<button id="paste-image-async-button">Paste image async</button>

JavaScript:

function copyImageAsync() {
    fetch("a.png")
        .then(response =&gt; {
            if (!response.ok) { // Check whether it is ok
                throw new Error(`HTTP error! Status: ${response.status}`);
            }

            return response.blob();
        })
        .then(blob =&gt; {
            console.log('blob.type =', blob.type);

            // On Safari, each ClipboardItem is initialized with
            // a mapping of MIME type to Promise.
            if (navigator.userAgent.includes("Safari")) { // On safari,
                navigator.clipboard.write([
                    new ClipboardItem({
                        [blob.type]: Promise.resolve(blob)
                    })
                ]);

            } else {
                navigator.clipboard.write([
                    new ClipboardItem({
                        [blob.type]: blob
                    })
                ]);
            }
        }).catch(err =&gt; console.error(err));;
}
document.querySelector("#paste-image-async-button")
    .addEventListener("click", copyImageAsync);

Note special process for ClipboardItem on Safari

WebKit’s Async Clipboard API: Each ClipboardItem is initialized with a mapping of MIME type to Promise (see ).

See more at webkit: Async Clipboard API. It also specify how to use the new Async Clipboard API and old document.execCommand("copy") on Safari.

Conclusion

Now you have known the main differences between document.execCommand() and the Clipboard API. To be compatible with most browsers, we need to use both methods to work with the clipboard access.

Below example is a variant of example in webkit: Async Clipboard API. It writes a given text to the clipboard which tries to be compatible to as many browsers as possible.

HTML

<button id="copy-given-text-button">Copy given text</button>

JavaScript

// Copy the given text to the clipboard
function copyGivenText(text) {
    try {
        if (!navigator.clipboard) {
            // First create a textarea element to hold the given text
            // and set its content being selected.
            // A他 remove it after copping the selection to the clipboard.

            let textarea = document.createElement("textarea");
            textarea.style.opacity = "0";
            textarea.style.position = "fixed";
            textarea.value = text;
            document.body.appendChild(textarea);

            textarea.focus();
            textarea.setSelectionRange(0, textarea.value.length);
            document.execCommand("copy");
            console.log("Copy successful");

            textarea.remove();
        } else {
            navigator.clipboard.writeText(text)
                .then(
                    () => console.log("Copy successful"),
                    () => console.log("Copy failed")
                );
        }
    } catch (err) {
        console.error(err);
    }
}
document.querySelector("#copy-given-text-button")
    .addEventListener("click", () => copyGivenText("Given text"));

Reference

  • MDN: Clipboard API

    > Note: This API is not available in Web Workers (not exposed via WorkerNavigator).

  • MDN: Interact with the clipboard

    The Clipboard API is only available to Secure Contexts (An active tab in the browser is also considered to be a secure context).

    Using the Clipboard API in an browser extension requires the permission "clipboardRead" or "clipboardWrite" in the manifest.json file.

  • web.dev: Unblocking clipppbard acess

    To use the Clipboard API in iframes, you need to pass either or both clipboard-read or clipboard-write like:


  • WebKit’s Async Clipboard API

    > Note that both clipboard.write and clipboard.writeText are asynchronous. If you attempt to write to the clipboard while a prior clipboard writing invocation is still pending, the previous invocation will immediately reject, and the new content will be written to the clipboard.

    Sanitization

    Both "text/html" and "image/png" data is sanitized before writing to the pasteboard.

    Similar to writing data, reading data from the system clipboard involves sanitization to prevent users from unknowingly exposing sensitive information.

  • w3.org: Async Clipboard API

JavaScript asynchronous and the event loop

This article walks you through all things about asynchronous JavaScript including why asynchronous is needed, how it works for a single-threaded JavaScript and how to do asynchronous programing. A lot of content is from MDN but reorganized for a better understanding.

JavaScript is an inherently single-threaded language. By default (unless you uses a web worker which is provided by the browser, not part of the JavaScript language) , the browser uses a single thread (the main thread) to run all the JavaScript in your page and to perform other stuff like event receiving/dispatching, UI updating. But for long-running operations, making them asynchronous is needed.

All content talked here if not specified, is about JavaScript in a browser environment.

Why asynchronous programming is needed?

A basic problem with a long-running synchronous function is that it will block the thread and make our program unresponsive. To avoid the function to block the thread we can make the long-running function being asynchronous like:

  • When it is called it returns immediately and our program can still be responsive.

  • When it eventually completes our program can be notified with its result.

Many functions provided by browsers take a long time, therefore they are asynchronous. Some of them are (see more ):

Note

Such Browser APIs are built into your web browser and are not part of the JavaScript language itself. They are built on top of the core JavaScript language and executed by the browser itself, not by the JavaScript engine which executes your JavaScript code.

See more in Introduction to web APIs – Learn web development | MDN.

How asynchronous functions are implemented?

About how asynchronous function is implemented, you might have thought of the way an event is handled. You are right, in fact some early asynchronous APIs used events in just this way.

JavaScript has a runtime model based on an event loop. The event loop processes tasks one after another. It is something like:

// queue.waitForMessage() waits synchronously for a message to arrive
// (if one is not already available and waiting to be handled).
while (queue.waitForMessage()) {
  queue.processNextMessage();
}

The event loop

JavaScript has a runtime model based on an event loop, which is responsible for executing the code, collecting and processing events, and executing queued sub-tasks.

When an event occurs, its handler(callback) will be added into the task queue and run at an appropriate time. This is really a form of asynchronous programming.

Some early asynchronous APIs used events in just this way. Like you call XMLHttpRequest to make a HTTP request to a remote server and you need to define a listener for the loadend event to get notified about the eventual completion of the request.

const xhr = new XMLHttpRequest();
xhr.addEventListener('loadend', () =&gt; {
    console.log(`Finished with status: ${xhr.status}`);
});

xhr.open('GET', 'https://example.com/example.json');
xhr.send();

The asynchronous function returns immediately and the request will be going on in the browser. At the same time our JavaScript code which is executed by the JavaScript engine continues to run. And our event handler will be called when the request is complete.

Task queue

Here it just mentions one queue for simplicity. In fact, there can be more than one queues so that events/messages can have different priorities and they are put into different queues.

Promise

Now you have known the way how asynchronous functions were implemented in JavaScript. However, sometimes callback-based code will become not elegant . Like we need preform an operation that needs a series of asynchronous functions, we nest callbacks like:

function doOperation() {
  doStep1(0, (result1) =&gt; {
    doStep2(result1, (result2) =&gt; {
      doStep3(result2, (result3) =&gt; {
        console.log(`result: ${result3}`);
      });
    });
  });
}

doOperation();

This is called “callback hell” and it needs to handle errors at each level.

For these reasons, most modern asynchronous APIs don’t use callbacks but Promise. But the mechanism behind is similar.

Promise

Basic usage

You can think a promise as an object returned by an asynchronous function. When the promise is returned, the operation often isn’t finished. But it allows you to attach handlers to this promise object. When the operation is finished, the promise will call our handler asynchronously, passing the final result or an error of the operation as the parameter.

Promise description

Promise is a proxy for a value not necessarily known when the promise is created. This lets asynchronous methods return values like synchronous methods, merely it returns a Promise and supply the final value in the future.

Below is an example to make a HTTP request to a remote server with the fetch() API that is based on promise.

const fetchPromise = fetch('https:example.com/p1.json');

// Here the fetchPromise's state is "pending"
console.log(fetchPromise); // Output: Promise {}

// When the fetch operation succeeds, fetchPromise gets resolved with
// a Response object returned by fetch as its value
// and fetchPromise calls our handler, passing its value as the parameter.
fetchPromise.then((response) =&gt; {
    // Output: Received response: 200
    console.log(`Received response: ${response.status}`);

    console.log(fetchPromise); // Output: Promise {: Response}
});

The then() takes up to two parameters: onFulfilled and onRejected. They are callback functions (handlers) for the success and failure cases of the Promise. Both of them are optional.

Note:

If onFulfilled is not a function, it is internally replaced with an identity function ((x) =&gt; x) which simply passes the fulfillment value forward.

If onRejected. is not a function, it is internally replaced with a thrower function ((x) =&gt; { throw x; }) which throws the rejection reason it received.

It returns a newly generated Promise. When a handler(onFulfilled/onRejected) is called, the promise gets resolved with the value returned by the handler as its value (if the handler does return a value and it is not a promise).

Basically if the handler is called and:

  • doesn’t return anything, the promise get resolved with undefined as its value.

  • returns another promise (let’s call it B), the promise (let’s call it A) returned by then() follows it.

    A follows B means if B gets resolved, A gets resolved, if B never gets resolved, A won’t either.

See more about the rules that the behavior of the handler follows at Promise.prototype.then() – JavaScript | MDN.

Chain promises

As you saw above, then() returns a promise so that it can be used for chaining.

Below is an examples which shows how to chain multiple promises to preform an operation that needs a series of asynchronous functions as mentioned in the previous section.

doAsyncStep1(0)
    .then((result1) =&gt; doAsyncStep2(result1))
    .then((result2) =&gt; doAsyncStep3(result2))
    .then((result3) =&gt; {
        console.log(`result: ${result3}`);
    });

In this example, each subsequent asynchronous function starts when the previous one succeeds, with the parameter comes from the previous step.

Always return result in chain

In the above example, each onFulfilled handler is an arrow function with only an expression which is the implicit return value.

In other cases, if the arrow function has a block body or a normal function, you need an explicit return statement, otherwise the subsequent handlers won’t get the result of the previous one. And a worse issue is that the next handler will be called earlier because the previous one returns nothing and make the corresponding promise gets resolved with undefined as its value.

See at Using Promises.

Below is an example that gets the response data after a request is completed. See at How to use promises – chaining promises

const fetchPromise = fetch('http://example.com/p1.json');

fetchPromise
    .then((response) => response.json()) // Here it does not handle
        // the situation that respone.ok is not true
    .then((data) => {
        console.log(data[0].name);
    });

The json() method of the Response object is also asynchronous and returns a promise.

More about the then()

Below is another example that shows the process of the then() is executed.

function foo() {
    const p = new Promise((resolve, reject) =&gt; {
        resolve('Success');
    });

    console.log('p', p); // p: Promise {:

    return p.then((value) =&gt; {
        console.log(value); // 'Success'
        return 'Success2';
    }, (e) =&gt; {
        console.log(e.message);
    });
}

const result = foo();
console.log('Result:', result); // Result: Promise {}
// By using setTimeout, we postpone the execution of `console.log(result)`
setTimeout(() =&gt; console.log(result), 0);

// Output:
// p: Promise {: 'Success'}
// Result: Promise {}
// Success
// Promise {: 'Success2'}

Here is how:

  1. When you call foo(), it first create a resolved promise p. It is printed in the console: p: Promise {: 'Success'}.

  2. After that the then() is called on the promise p. then() creates and returns a pending promise which is then returned by foo().

    Note the handlers added to then() are always called asynchronously. Even though here p is already fulfilled, the handler of p.then() will not be called immediately but is put on a microtask queue. See more at Using Promises: Timing.

  3. The pending promise is printed in the console: Result: Promise {}.

  4. setTimeout postpone the execution of console.log(result).

  5. At some point, onFulfilled handler of then() is called, and Success is printed in the console. The promise returned by then() gets resolved with Success as its value.

  6. At some point, console.log(result) is executed and the promise which has get resolved is printed in the console:

    {: 'Success'}

In the example, if p is a rejected promise that is:

const p = new Promise((resolve, reject) => {
    // or throw new Error(’fail');
    reject(new Error('Fail'));
});

The output would be:

// p: Promise {<rejected>: Error: Fail
// Result: Promise {<pending>}
// Fail
// Promise {<fulfilled>: undefined}

Attaching handlers for a promise with then() is like defining a handler for an event. The handler in then() is called asynchronously at some point in the event loop.

Using Promises – Guarantees

Unlike old-fashioned passed-in callbacks, a promise comes with some guarantees:

  • Callbacks added with then() will never be invoked before the completion of the current run of the JavaScript event loop.
  • These callbacks will be invoked even if they were added after the success or failure of the asynchronous operation that the promise represents.
  • Multiple callbacks may be added by calling then() several times. They will be invoked one after another, in the order in which they were inserted.

See more at Promise.prototype.then() and Let’s talk about how to talk about promises.

Error handling with catch()

The Promise object provide a catch() method to handle errors. Like then(), the handler passed to catch() is called when the asynchronous operation fails.

An example with error handling.

const fetchPromise = fetch('https:example.com/p1.json');

fetchPromise
    .then((response) =&gt; {
        if (!response.ok) {
            throw new Error(`HTTP error: ${response.status}`);
        }
        return response.json();
    })
    .then((data) =&gt; {
        console.log(data[0].name);
    })
    .catch((error) =&gt; {
        console.error(`Could not get products: ${error}`);
    });

finally():

The finally() method can be useful if you want to do some processing or cleanup once the promise is settled, regardless of its outcome.

The main differences between finally(finally) and then(onFinally, onFinally) it that the return value of the handler of finally() does not affect the state of the original promise, unless the handler throws an error or returns a rejected promise.

// The returned promise will gets resolved with 2
console.log( Promise.resolve(2).finally(() =&gt; 77) );

// The returned promise will gets resolved with 2
console.log( Promise.resolve(2).finally(() =&gt; Promise.resolve(77)) );

// The returned promise will gets rejected with 99
console.log( Promise.reject(3).finally(() =&gt; Promise.reject(9
9)) );
// The returned promise will gets rejected with 99
console.log( Promise.reject(3).finally(() =&gt; {throw 99}) );

About return value

catch() also returns a Promise and it can be used for chaining too. But unlike then(), it has one handler and the handler may have no chance to be called. This may remind you a question. Like the example showed in the previous section, if a function returns a promise with catch() attached then how the returned promise gets resolved.

Promise.prototype.catch() tells that catch() internally calls then() and gives an example to observe that. More clearly, catch() internally calls then() with parameters undefined and its own onRejected handler. And it returns the value that then() returns.

In fact, catch() acts just like a then(undefined, onRejected). Hence it is clear what happens in the example below.

An example in which a catch() is chained to a promise.

function foo() {
    const p = new Promise((resolve, reject) =&gt; {
        resolve('Success');
    });

    console.log('p:', p); // p: Promise {: 'Success'}

    return p.catch((e) =&gt; {
        console.error('Fired in catch:', e.message); // Never called.
    });
}
const result = foo();
console.log('Result:', result); // Result: Promise {}
// By using setTimeout, we postpone the execution of `console.log(result)`
setTimeout(() =&gt; console.log(result), 0); // Promise {: 'Success'}

// Output:
// p: Promise {: 'Success'}
// Result: Promise {}
// Promise {: 'Success'}

Promise – JavaScript | MDN:

.catch() is really just a .then() without a slot for a callback function for the case when the promise is fulfilled.

A rejected callback in then() or a catch()

If there is no need to handle errors immediately, you can just leave out an error in a promise chain to fall back to the final catch(). Let’s rewrite the example by adding error handling:

doAsyncStep1(0)
    .then((result1) =&gt; doAsyncStep2(result1))
    .then((result2) =&gt; doAsyncStep3(result2))
    .then((result3) =&gt; {
        console.log(`result: ${result3}`);
    })
    .catch((error) =&gt; {
        // The error callback will be called when any of the
        // asynchronous doAsyncStepX calls fails.
        console.error(error);
    });

If you need to handle an error immediately, just provide a rejection callback to a proper then(). Note if that rejection callback is called, you need to throw an error to make the error down the chain, otherwise the promise returned by that then() will get fulfilled.

Gotchas when throwing errors:

Errors thrown inside asynchronous functions will act like uncaught errors:

const p2 = new Promise((resolve, reject) =&gt; {
    setTimeout(() =&gt; {
        throw new Error("Uncaught Exception!");
    }, 1000);
});

p2.catch((e) =&gt; {
    console.error(e); // This is never called
});

Another eample:

function foo(request) {
    // caches.match() gets the reponse for a request in the caches.
    return caches.match(request)
        .then(undefined, error =&gt; {
            // Error in the asynchronous open() won't be caught
            // in the final catch.
            caches.open();

            // Its error will be caught in the final catch,
            // because the promise A returned by then() follows
            // the promise B returned by match(). When B gets
            // gets rejected, A gets rejected either and
            // the handler in the final catch will be called.
            return catches.match('/default.jpg');
        })
        .catch(function (error) {
            console.log('final match error:', error);
        });
}

Errors thrown after resolve is called will be silenced:

const p3 = new Promise((resolve, reject) =&gt; {
    resolve();
    hrow new Error("Silenced Exception!");
});

p3.catch((e) =&gt; {
    console.error(e); // This is never called
});

Promise.all() and Promise.any()

If you need to be notified when all the promises to be fulfilled which don’t depend on each other, Promise.all() is what you want.

Promise.all() is a static method which takes an array of promises and returns a single promise. The returned promise is fulfilled when and if all the promises in the array are fulfilled, and it is rejected when and if any of the promises is rejected.

Example:

const fetchPromise1 = fetch('https://example.com/p1.json');
const fetchPromise2 = fetch('https://example.com/p2.json');
const fetchPromise3 = fetch('https://example.com/p3.json');

Promise.all([fetchPromise1, fetchPromise2, fetchPromise3])
    .then((responses) =&gt; {
        for (const response of responses) {
            console.log(`${response.url}: ${response.status}`);
        }
    })
    .catch((error) =&gt; {
        console.error(`Failed to fetch: ${error}`)
    });

If you need any one of a set of promises to be fulfilled not all, you will want Promise.any(). Its usage is similar and not listed hare any more.

States of a promise

A promise can be in one of three states:

  • pending: initial state, the asynchronous function has not completed.
  • fulfilled: the asynchronous function has succeeded, the first handler(onFulfillment parameter) passed to then() is called.
  • rejected: the asynchronous function has failed, its catch() handler is called.

Terms: settled, resolved

A promise is settled if it is fulfilled or rejected.

A promise is resolved if it is settled or it has been “locked in” to follow the state of another promise (by attaching then() to it).

See more in Let’s talk about how to talk about promises.

The eventual state of a pending promise can either be fulfilled with a value or rejected with a reason (error).

See more at Promise and How to use promises.

Promise constructor

You have seen how to create a Promise in the examples mentioned above. It accepts a function called executor which will be executed by the constructor. The function executor accepts will receive two functions  resolutionFunc and rejectionFunc as parameters.

The constructor returns a promise which will become resolved when either of the functions resolutionFunc or rejectionFunc are invoked.

Note that when you call resolutionFun(value) or rejectionFunc(value), the value can also be another promise, in which case the promise gets dynamically inserted into the promise chain (In such case, the constructed promise is resolved but still not settled).

Below example wraps a callback-style asynchronous to support promise.

function myAsyncFunction(url) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open("GET", url);
        xhr.onload = () => resolve(xhr.responseText);
        xhr.onerror = () => reject(xhr.statusText);
        xhr.send();
    });
}

See more at MDN: Promise() constructor.

Browser support of promises

JavaScript Promises: an introduction:

Browser support & polyfill

There are already implementations of promises in browsers today.

As of Chrome 32, Opera 19, Firefox 29, Safari 8 & Microsoft Edge, promises are enabled by default.

To bring browsers that lack a complete promises implementation up to spec compliance, or add promises to other browsers and Node.js, check out the polyfill (2k gzipped).

Async and await

The async and await keywords provides a cleaner way to work with promise-based code, avoiding explicitly configuring promise chains.

Adding async at the start of a function makes it an async function. Inside an async function, you can put await before a call to a function that returns a promise.

Let’s see an example to find out the effect of a await expression.

async function foo() {
    console.log('foo'); // It will be executed synchronously.

    // An await expression will make the progress suspended.
    const result1 = await new Promise((resolve) =>
        setTimeout(() => resolve('1'))
    );
    console.log('result1 =', result1); // Called asynchoronously

    const result2 = await new Promise((resolve) =>
        setTimeout(() => resolve('2'))
    );
    console.log('result2 =', result2);
}

console.log('before foo');
foo(); // Here foo() returns a promise with pending state.
console.log('after foo');

// Output:
// before foo
// foo
// after foo
// result1 = 1
// result2 = 2

Code after each await expression (including the assignment of the await expression) acts like the code inside a handler of a then().

The effect is equivalent to:

function foo() {
    console.log('foo');

    new Promise((resolve) =>
        setTimeout(() => resolve('1'))
    ).then((result1) => {
        console.log('result1 =', result1);
    }).then(() => {
        return new Promise((resolve) =>
           setTimeout(() => resolve('2'))
        );
    }).then((result2) => {
        console.log('result2 =', result2);
    })
}

await

Note that you can only use await inside an async function, unless your code is in a JavaScript module.

Return value of an async function

An async function always return a promise.

If the async function returns:

  • returns a value that is not a promise, the promise will gets resolved with that value.

  • throws an error, or uncaught within, the promise gets rejected with the error.

  • returns nothing, the promise will not gets resolved or rejected, i.e. remains pending.

Error handling

async function foo() {
    console.log('foo');

    const result1 = await new Promise((resolve) =>
        setTimeout(() => resolve('1'))
    );
    console.log('result1 =', result1);

    const result2 = await new Promise((resolve, reject) => {
        setTimeout(() => reject('fail'));
    });
    console.log('result2 =', result2);
}

console.log('before foo');
foo()
    .catch((e) => console.log('catch:', e));
console.log('after foo');

// Output:
// before foo
// foo
// after foo
// result1 = 1
// catch: fail

If there is no catch in the example:

console.log('before foo');
var fooResult = foo();
console.log('after foo');
setTimeout(() => console.log('after foo, fooResult =', fooResult), 1000);

// Output:
// before foo
// foo
// after foo
// result1 = 1
// Uncaught (in promise) fail
// after foo, fooResult = Promise {<rejected>: 'fail'}

An parallel example

async function concurrentStart() {
    console.log("==CONCURRENT START with await==");
    const slow = resolveAfter2Seconds(); // starts timer immediately
    const fast = resolveAfter1Second(); // starts timer immediately

    // 1. Execution gets here almost instantly
    console.log(await slow); // 2. this runs 2 seconds after 1.
    console.log(await fast); // 3. this runs 2 seconds after 1., immediately after 2., since fast is already resolved
}

Async function vs a function returns a Promise

Async function vs a function that returns a Promise (2018):

Async function vs a function returns a Promise

There is a small, but quite important difference between a function that just returns a Promise, and a function that was declared with the async keyword.

Take a look at the following snippet:

function fn(obj) {
    const someProp = obj.someProp
    return Promise.resolve(someProp)
}

async function asyncFn(obj) {
    const someProp = obj.someProp
    return Promise.resolve(someProp)
}

asyncFn().catch(err =&gt; console.error('Catched')) // =&gt; 'Catched'
fn().catch(err =&gt; console.error('Catched')) // =&gt; TypeError: Cannot read proper

JavaScript will make sure that the asnycFn will return with a Promise (either resolved or rejected) even if an error occurred in it, in our case calling our .catch() block.

However with the fn function the engine doesn’t yet know that the function will return a Promise and thus it will not call our catch() block.

Workers

A worker enables you to run some tasks in a separate thread. Workers are Web APIs that are provided by the browser and not part of the JavaScript language.

A challenge with multithreaded code is that two threads may access the same variables and causes an inconsistence issue.

Introducing workers: To avoid this on the web, your main code and your worker code never get direct access to each other’s variables. Workers and the main code run in completely separate worlds, and only interact by sending each other messages. In particular, this means that workers can’t access the DOM (the window, document, page elements, and so on).

There are three different sorts of workers:

  • Dedicated workers
  • Shared workers can be shared by several different scripts running in different windows.
  • Service workers act like proxy servers, caching resources so that web applications can work when the user is offline. They’re a key component of Progressive Web Apps.

The last two types of workers are not introduced here.

Web worker

An simplified example from Introducing workers that use a worker to generates prims.

generate.js:

// Listen for messages from the main thread.
// If the message command is "generate", call `generatePrimes()`
addEventListener('message', (message) =&gt; {
    if (message.data.command === 'generate') {
      generatePrimes(message.data.quota);
    }
});

// A synchronous function which generates primes (very inefficiently)
// of a count specified by quoto.
function generatePrimes(quota) {
    // Generate primes and put them into an array primes
    // const primes = [];
    // ...

    // When we have finished, send a message to the main thread,
    // including the number of primes we generated.
    postMessage(primes.length);
}

In the above code, we start listening a message from the main script. If a message is sent, in the handler we reads the data property which is a copy from the main script and call generatePrimes. When the prims are done, we use postMessage() – Web APIs and pass it the count of prims generated to send a message to the main script. Similar, the data we passes to postMessage is a copy from the worker to the main script.

main.js:

// Create a new worker, giving it the code in "generate.js"
const worker = new Worker('./generate.js');

worker.postMessage({
    command: 'generate',
    100,
});

// When the worker sends a message back to the main thread,
// print the number of primes that were generated,
// taken from the message data.
worker.addEventListener('message', (message) =&gt; {
    console.log(`Finished generating ${message.data} primes!`);
});

Reference

JavaScript Runtime and the event loop

  • MDN: The event loop

    > ### Stack
    >
    > Function calls form a stack of frames.
    >
    > Note that the arguments and local variables may continue to exist, as they are stored outside the stack — so they can be accessed by any nested functions long after their outer function has returned.
    >
    > ### [“Run-to-completion”](The event loop – JavaScript | MDN “Permalink to “Run-to-completion””)
    >
    > Each message is processed completely before any other message is processed.
    >
    > This offers some nice properties when reasoning about your program, including the fact that whenever a function runs, it cannot be preempted and will run entirely before any other code runs (and can modify data the function manipulates).
    >
    > ### Several runtimes communicating together
    >
    > A web worker or a cross-origin iframe has its own stack, heap, and message queue. Two distinct runtimes can only communicate through sending messages via the postMessage method. This method adds a message to the other runtime if the latter listens to message events.
    >
    > ### Never blocking
    >
    > A very interesting property of the event loop model is that JavaScript, unlike a lot of other languages, never blocks. Handling I/O is typically performed via events and callbacks, so when the application is waiting for an IndexedDB query to return or an XHR request to return, it can still process other things like user input.

  • MDN: Asynchronous

    > Introducing asynchronous JavaScript
    >
    > In this article, we’ll learn about synchronous and asynchronous programming, why we often need to use asynchronous techniques, and the problems related to the way asynchronous functions have historically been implemented in JavaScript.
    >
    > How to use promises
    >
    > Here we’ll introduce promises and show how to use promise-based APIs. We’ll also introduce the async and await keywords.
    >
    > Implementing a promise-based API
    >
    > This article will outline how to implement your own promise-based API.
    >
    > Introducing workers
    >
    > Workers enable you to run certain tasks in a separate thread to keep your main code responsive. In this article, we’ll rewrite a long-running synchronous function to use a worker.

  • MDN: In depth: Microtasks and the JavaScript runtime environment

    > Starting with the addition of timeouts and intervals as part of the Web API (setTimeout() and setInterval()), the JavaScript environment provided by Web browsers has gradually advanced to include powerful features that enable scheduling of tasks, multi-threaded application development, and so forth. To understand where queueMicrotask() comes into play here, it’s helpful to understand how the JavaScript runtime operates when scheduling and running code.
    >
    > ### JavaScript execution contexts
    >
    > When a fragment of JavaScript code runs, it runs inside an execution context.
    >
    > There are three types of code that create a new execution context:
    >
    > – The global context is the execution context created to run the main body of your code; that is, any code that exists outside of a JavaScript function.
    > – Each function is run within its own execution context. This is frequently referred to as a “local context.”
    > – Using the ill-advised eval() function also creates a new execution context.
    >
    > As one code segment begins execution, a new context is constructed and is placed on the execution context stack. When it exits, the context is removed from the context stack.
    >
    > Using execution contexts in this manner, each program and function is able to have its own set of variables and other objects.
    >
    > ### Event loops
    >
    > Each agent is driven by an event loop, which collects any user and other events, enqueuing tasks to handle each callback. It then runs any pending JavaScript tasks, then any pending microtasks, then performs any needed rendering and painting before looping again to check for pending tasks.
    >
    > ### Task queue vs microtask queue
    >
    > The difference between the task queue and the microtask queue is simple but very important:
    >
    > – When executing tasks from the task queue, the runtime executes each task that is in the queue at the moment a new iteration of the event loop begins. Tasks added to the queue after the iteration begins will not run until the next iteration.
    > – Each time a task exits, and the execution context stack is empty, each microtask in the microtask queue is executed, one after another. The difference is that execution of microtasks continues until the queue is empty—even if new ones are scheduled in the interim. In other words, microtasks can enqueue new microtasks and those new microtasks will execute before the next task begins to run, and before the end of the current event loop iteration.

  • MDN: Using microtasks

  • The Node.js Event Loop, Timers, and process.nextTick()

    > ## What is the Event Loop?
    >
    > The event loop is what allows Node.js to perform non-blocking I/O operations — despite the fact that JavaScript is single-threaded — by offloading operations to the system kernel whenever possible.
    >
    > ## Event Loop Explained
    >
    > When Node.js starts, it initializes the event loop, processes the provided input script (or drops into the REPL, which is not covered in this document) which may make async API calls, schedule timers, or call process.nextTick(), then begins processing the event loop.
    >
    > The following diagram shows a simplified overview of the event loop’s order of operations.
    >
    > sql
    &gt; ┌───────────────────────────┐
    &gt; ┌─&gt;│ timers │
    &gt; │ └─────────────┬─────────────┘
    &gt; │ ┌─────────────┴─────────────┐
    &gt; │ │ pending callbacks │
    &gt; │ └─────────────┬─────────────┘
    &gt; │ ┌─────────────┴─────────────┐
    &gt; │ │ idle, prepare │
    &gt; │ └─────────────┬─────────────┘ ┌───────────────┐
    &gt; │ ┌─────────────┴─────────────┐ │ incoming: │
    &gt; │ │ poll │ │ └─────────────┬─────────────┘ │ data, etc. │
    &gt; │ ┌─────────────┴─────────────┐ └───────────────┘
    &gt; │ │ check │
    &gt; │ └─────────────┬─────────────┘
    &gt; │ ┌─────────────┴─────────────┐
    &gt; └──┤ close callbacks │
    &gt; └───────────────────────────┘
    &gt;

Workers

Web API

  • How does the Event Loop works in JavaScript? 2021.03

    Much its content is like a text version of a video: Philip Roberts: Help, I’m stuck in an event-loop. 2014.05.25. The author of the video Philip Roberts created a website Loupe to show how the event loop works for a given piece of code.

    > ### The Web API
    >
    > This is where code that isn’t handled by the V8 engine is executed to not “block” the main execution thread. When the Call Stack encounters a web API function, the process is immediately handed over to the Web API, where it is being executed and freeing the Call Stack to perform other operations during its execution.

  • Client-side web APIs

    > ### APIs in client-side JavaScript
    >
    > Client-side JavaScript, in particular, has many APIs available to it — these are not part of the JavaScript language itself, rather they are built on top of the core JavaScript language, providing you with extra superpowers to use in your JavaScript code. They generally fall into two categories:
    >
    > – Browser APIs are built into your web browser and are able to expose data from the browser and surrounding computer environment and do useful complex things with it.
    >
    > – Third-party APIs are not built into the browser by default.  For example, the Twitter API allows you to do things like displaying your latest tweets on your website.

  • Web APIs  a list of all the APIs and interfaces (object types).

Async funciton

JavaScript data storage — localStorage, IndexedDB and CacheStorage

This article gives all you want to know about different data storage including localStorage, sessionStorage, IndexedDB and Cache. After reading this post, you will get to know the differences among them, which storage should you use, how to use them, how much data can I store, etc.

Overview

An overview of the storage types.

  • Web Storage
    • sessionStorage Temporary session data

    • Store key/value pair only for a session, data expires when browser(or tab) is closed.

    • The keys and values are always in the UTF-16 string format.

    • localStorage Small data

    • Store key/value pair persistently.

    • The keys and values are always in the UTF-16 string format.

    • Useful for storing small amounts of data.

  • [IndexedDB](IndexedDB API – Web APIs | MDN) Large data

    • A transactional database system,

    • Powerful but complicated for simple cases.

    • Useful for storing large amounts of structured data.

  • Cache Resource caching

    • Store Request / Response object pairs, representing HTTP requests and responses.
    • Useful in URL addressable resource caching, like assets, HTTP API responses.

Note

Cookies should not be used for storage. They are sent with every HTTP request. If storing data with it will significantly increase the size of a HTTP request.

Deprecated API for storage

Differences among data storage types

Here are differences among the 4 types of data storage.

Storage Expire Asynchronous Size limit Security Web workers
sessionStorage ✓ (Yes) ✗ (no) 5MB Same tab ✗ (No)
localStorage ✗ (No)* ✗ (No) Same origin ✗ (No)
IndexedDB ✗ (No)* ✓ (Yes) Large Same origin ✓ (Yes)
Cache ✗ (No)* ✓ (Yes) ✓ (Yes)

More about expire

If storage limit is reached, data may be cleared out depending on storage type and the browser.

For Safari, data will be deleted after 7 days of use.

webkit: 7-Day Cap on All Script-Writeable Storage

In iOS and iPadOS 13.4 and Safari 13.1 on macOS, it will delete all of a website’s script-writable storage after seven days of Safari use without user interaction on the site. These are the script-writable storage forms affected (excluding some legacy website data types):

  • Indexed DB
  • LocalStorage
  • Media keys
  • SessionStorage
  • Service Worker registrations and cache

More about security

Access to Web Storage from third-party IFrames is denied if the user has disabled third-party cookies (Firefox implements this behavior from version 43 onwards.)

Note

Cookies use a separate definition of origins compared to that used by Web Storage and IndexedDB. A page can set a cookie for its own domain or any parent domain, as long as the parent domain is not a public suffix.

Storage limit

The global limit for all kinds of storage is dynamic and depends on the browser. According to Storage for the web:

  • Firefox:  50% of free disk space.

  • Chrome: 80% of total disk space. An origin can use up to 60%

  • Safari (both desktop and mobile): appears to allow about 1GB.

What happen if the storage limit is reached?

Depending on the browser, data eviction may be triggered or a QuotaExceededError may be thrown out. The data eviction is based on an LRU policy — the least recently used origin will be deleted first, then the next one, until the browser is no longer over the limit.

See MDN: Browser storage limits and eviction criteria for more information.

Persistent storage

Storage for the web:

You can request persistent storage for your site to protect critical user or application data. Unless the user removes it, you can protect it from eviction.

// Check if site's storage has been marked as persistent
if (navigator.storage &amp;&amp; navigator.storage.persist) {
    const isPersisted = await navigator.storage.persisted();
    console.log(`Persisted storage granted: ${isPersisted}`);
}

// Request persistent storage for site
if (navigator.storage &amp;&amp; navigator.storage.persist) {
    const isPersisted = await navigator.storage.persist();
    console.log(`Persisted storage granted: ${isPersisted}`);
}

How much storage space is taken and available?

In many browsers, you can use [StorageManager API](StorageManager.estimate() – Web APIs | MDN) to asks the Storage Manager for how much storage the current origin takes up (usage), and how much space is available (quota).

This method operates asynchronously, so it returns a Promise which resolves once the information is available. The promise’s fulfillment handler is called with an object containing the usage and quota data.

Example

 if (navigator.storage && navigator.storage.estimate) {
    navigator.storage.estimate().then(function(estimate) {
        console.log('You are currently using about '
            + (estimate.usage / estimate.quota * 100).toFixed(2)
            + '% of your available storage.');
        console.log('You can write ' + (estimate.quota - estimate.usage)
            + ' more bytes.');
    });
}

Where is the storage data stored?

The actual storage is stored at a SQLite file under directory /.

MDN: Browser storage limits and eviction criteria:

Where is the data stored?

Each storage type represents a separate repository. Here’s the actual mapping to directories under a user’s Firefox profile (other browsers may differ slightly):

  • /storage — the main top-level directory for storages maintained by the quota manager (see below)
  • /storage/permanent — persistent data storage repository
  • /storage/temporary — temporary data storage repository
  • /storage/default — default data storage repository

The profile folder for browsers:

  • Firefox

    In Firefox, enter about:support in the URL bar to open the “Troubleshooting Information” tab. You will find the profile folder is displayed in the “Application Basics” table like below (Firefox v99/Win10):

Profile Folder  C:UsersAppDataRoamingMozillaFirefoxProfileskxx.default-release-xxx

Or you can open the “Troubleshooting Information” page by

Firefox menu &gt; Help &gt; More Troubleshooting Information.

See more about what information is stored in the profile at Firefox Profile.

An example of a localStorage file (Firefox v99/win10)

/
|- ...
|- cookies.sqlite // ---- Cookies
|- storage/
|- archives/
|- default/ // ---- Kinds of storage for sites
|- https+++xx1.com/
|- cache/
|- idb/
|- ls/ // ---- LocalStorage for xx1.com is saved here
|- data.sqlite // You can open it with a SQLite browser
|- ...
|- permanent/
|- temporary/

In the example above, the LocalStorage data for https://xx1.com` is stored in a SQLite database file --data.sqlite`. You can open it with a SQLite browser. You will see the same data as you inspect it in the DevTools (in Storage tab) of Firefox.

  • Chrome

    The profile for chrome:

    C:UsersAppDataLocalGoogleChromeUser Data

Storage inspect in DevTools

In DevTools, you can inspect the different storage types and clear data. Chrome v88 provides a feature for you to simulate storage quota. See Storage inspect (The content is also quoted in the end of the article) for more details.

sessionStorage

Opening multiple tabs/windows with the same URL creates sessionStorage for each tab/window. It means sessionStorage does not share between multiple tabs/windows.

Availability detecting

See the same section of localStorage.

Basic usage example

// Save data to sessionStorage
sessionStorage.setItem('key', 'value');

// Get saved data from sessionStorage
let data = sessionStorage.getItem('key');

// Remove saved data from sessionStorage
sessionStorage.removeItem('key');

// Remove all saved data from sessionStorage
sessionStorage.clear();

To write/read some data rather than string, use JSON.stringify() to convert it to string and use JSON.parse() to convert it from string.

localStorage

localStorage is similar to sessionStorage, except that while localStorage data has no expiration time.

Availability detecting

Before using localStorage, it is necessary to detect whether it is available, because it may throw exceptions like Failed to read the loacalStorag in various situations. Below are some of them:

  • localStorage is not supported in an early version of a browser.

  • The user disables web storage in the browser settings, like the cookie is blocked on Chrome.

  • In a browser’s private browsing mode.

MDN gives a function that detects whether localStorage or sessionStorage is both supported and available:

function storageAvailable(type) {
    var storage;
    try {
        storage = window[type];
        var x = '__storage_test__';
        storage.setItem(x, x);
        storage.removeItem(x);
        return true;
    }
    catch(e) {
        return e instanceof DOMException && (
            // everything except Firefox
            e.code === 22 ||
            // Firefox
            e.code === 1014 ||
            // test name field too, because code might not be present
            // everything except Firefox
            e.name === 'QuotaExceededError' ||
            // Firefox
            e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
            // acknowledge QuotaExceededError only if there's something already stored
            (storage && storage.length !== 0);
    }
}

And here is how you would use it:

if (storageAvailable('localStorage')) {
  // Yippee! We can use localStorage awesomeness
}
else {
  // Too bad, no localStorage for us
}

Call storageAvailable('sessionStorage') for sessionStorage.

Basic usage example

// Add a localStorage item
localStorage.setItem('myCat', 'Tom');

// Read the localStorage item
const cat = localStorage.getItem('myCat');

// Remove the localStorage item.
localStorage.removeItem('myCat');

To write/read some data rather than string, use JSON.stringify() to convert it to string and use JSON.parse() to convert it from string.

IndexedDB

IndexedDB is a way for you to persistently store structured data , including files/blobs, inside a user’s browser. It can work both online and offline.

Usage

IndexedDB is a low-level API which means it is not easy to use. There are some wrappers of IndexedDB to improve usability, below are some:

  • localForage: A Polyfill providing a simple name:value syntax for client-side data storage, which uses IndexedDB in the background, but falls back to WebSQL and then localStorage in browsers that don’t support IndexedDB.

  • idb: A tiny (~1.15k) library that mostly mirrors the IndexedDB API, but with small improvements that make a big difference to usability.

  • Dexie.js: A wrapper for IndexedDB that allows much faster code development via nice, simple syntax.

  • idb-keyval: A super-simple-small (~600B) promise-based keyval store implemented with IndexedDB

  • sifrr-storage: A small (~2kB) promise based library for client side key-value storage. Works with IndexedDB, localStorage, WebSQL, Cookies. Can automatically use supported storage available based on priority.

  • JsStore: A simple and advanced IndexedDB wrapper having SQL like syntax.

MDN provides a full IndexedDB example:

Availability detecting

if (window.indexedDB) {
    console.log("Your browser doesn't support a stable version of IndexedDB. Such and such feature will not be available.");
}

Cache

Cache is designed to store Request / Response object pairs. It is useful in resource caching.

An origin can have multiple, named Cache objects.

Size limit

Each browser has a hard limit on the amount of cache storage that a given origin can use.

You are responsible for periodically purging cache entries. See Deleting old caches for more information.

Availability detecting

if (window.caches) {
    // ...
}

Basic usage example

Use CacheStorage.open() to open a specific named Cache object and then call any of the Cache methods to maintain the Cache.

You can access CacheStorage through the global caches property.

API

// Open a cache
// If the specified Cache does not exist, a new cache is created
// with that cacheName and a Promise that resolves to this new
// Cache object is returned.
caches.open('my-cache').then(cache => {
    // Do things with the cache
});

// Add resource to the cache
caches.open('my-cache').then(cache => {
    fetch(url).then(function(response) {
        if (response.ok) {
            cache.put(url, response);
        }
    });
});

// Obtain the resouce associated with an URL and add it to the cache.
// add(url) takes a URL, retrieves it, and adds the resulting response
// object to the given cache.
caches.open('my-cache').then(cache => {
    cache.add(url);
});

// Retrieve a response associated with a request
caches.open('my-cache').then(cache => {
    cache.match(url).then(res => {
        //...
    });
});

// Delete
caches.open('my-cache').then(cache => {
    cache.delete(ur);
});

// Delete cache
caches.delete('my-cache').then(() => {
    // Deleted
});

Example

A simple example variant from MDN:

async function getCacheFirst(request) {
    var cacheAvailable = window.caches ? true : false;
    var response = await caches.match(request).catch((err) => {
        cacheAvailable = false;
        return null;
    });

    if (response) {
        return response;
    }

    // Not found in the caches or the caches are blocked.
    return await fetch(request).then(res => {
        if (cacheAvailable) {
            var res2 = res.clone();
            caches.open('v1').then((cache) => {
                cache.put(request, res2);
            });
        }

        return res;
    }).catch(err => {
        return null;
    });
}

In the code above, clone() is needed because put() consumes the response body.

Note:

The default example code from MDN has issues if not updated. It fails with TypeError or DOMException.

  • If the caches are available (not blocked by the browser), then it fails at cache.put(event.request, response) with Uncaught TypeError because the parameter response is null , it has not been saved in the cache yet.

  • If the caches are blocked by the browser (Like the cookies are blocked for the website in the settings of Chrome), it runs the first catch statement and then fails at caches.open('v1') in the then block with Uncaught DOMException because the cache is not allowed to access.

const cachedResponse = caches
  .match(event.request)
  .catch(() =&gt; fetch(event.request))
  .then((r) =&gt; {
    response = r;
    caches.open("v1").then((cache) =&gt; {
      cache.put(event.request, response);
    });
    return response.clone();
  })
  .catch(() =&gt; caches.match("/gallery/myLittleVader.jpg"));

You can download the whole simple-service-worker source code from simple-service-worker example.

MDN: Cache – Example is another example

Reference

  • MDN: Web Storage API
    • sessionStorage

    • Storage limit is larger than a cookie (at most 5MB).

    • localStorage

  • MDN: Using the Web Storage API

  • MDN: Using IndexedDB

  • MDN: IndexedDB API

  • MDN: Browser storage limits and eviction criteria

    > Talks about:
    >
    > – How much space to allocate to web data storage?
    >
    > – What to delete when that limit is reached?
    >
    > – Where is the data stored?
    >
    > Each storage type represents a separate repository. Here’s the actual mapping to directories under a user’s Firefox profile (other browsers may differ slightly):

  • MDN: Cache

  • MDN: CacheStorage

    The CacheStorage interface represents the storage for Cache objects.

  • MDN: Cross-Origin Resource Sharing (CORS)

  • MDN: Same-origin policy

    Set the value of document.domain…, and changing the origin in this way does not affect the origin checks used by many Web APIs (e.g. localStorageindexedDBBroadcastChannelSharedWorker)

    > ## Cross-origin data storage access
    >
    > Access to data stored in the browser such as Web Storage and IndexedDB are separated by origin. Each origin gets its own separate storage, and JavaScript in one origin cannot read from or write to the storage belonging to another origin.
    >
    > Cookies use a separate definition of origins. A page can set a cookie for its own domain or any parent domain, as long as the parent domain is not a public suffix.

  • Storage for the web

    sessionStorage and localStorage are not accessible from web workers or service workers.

    > ### Storage inspect
    >
    > During development, you can use your browser’s DevTools to inspect the different storage types, and easily clear all stored data.
    >
    > A new feature was added in Chrome 88 that lets you override the site’s storage quota in the Storage Pane. This feature gives you the ability to simulate different devices and test the behavior of your apps in low disk availability scenarios. Go to Application then Storage, enable the Simulate custom storage quota checkbox, and enter any valid number to simulate the storage quota.
    >
    > While working on this article, I wrote a simple tool to attempt to quickly use as much storage as possible. It’s a quick and easy way to experiment with different storage mechanisms, and see what happens when you use all of your quota.

JavaScript type

The latest ECMAScript standard defines 1 object data type and 7 primitive data types.

The 7 primitive data types are:

  1. Boolean. true and false.
  2. null. A special keyword denoting a null value. (Because JavaScript is case-sensitive, null is not the same as Null, NULL, or any other variant.)
  3. undefined. A top-level property whose value is not defined.
  4. Number. An integer or floating point number. For example: 42 or 3.14159.
  5. BigInt. An integer with arbitrary precision. For example: 9007199254740992n.
  6. String. A sequence of characters that represent a text value. For example: “Howdy”
  7. Symbol (new in ECMAScript 2015). A data type whose instances are unique and immutable.

ECMAScript has two built-in numeric types: Number and BigInt.

Note:

All primitives (the values themselves not the variables) are immutable. A variable can be reassigned a primitive value, but the existing primitive value can not be changed like an object.

validate

https://dmitripavlutin.com/javascript-defined-variable-checking/

// Examples of defined variables:

const pi = 3.14; // pi is defined
let result;      // result is defined

window.message = 'Hello';
message;         // message is defined

Find whether a variable is defined or not.

The typeof operator determines the variable’s type. typeof myVar can evaluate to one of the values:

  • 'boolean'

  • 'number'

  • 'string'

  • 'symbol'

  • 'object'

  • 'function'

  • 'undefined'.

// missingVar is not defined

typeof missingVar; // Doesn't throw ReferenceError

missingVar;        // Throws ReferenceError

942

https://stackoverflow.com/questions/5113374/javascript-check-if-variable-exists-is-defined-initialized

The typeof operator will check if the variable is really undefined.

if (typeof variable === 'undefined') {
    // variable is undefined
}

The typeof operator, unlike the other operators, doesn’t throw a ReferenceError exception when used with an undeclared variable.

However, do note that typeof null will return "object". We have to be careful to avoid the mistake of initializing a variable to null. To be safe, this is what we could use instead:

if (typeof variable === 'undefined' || variable === null) {
    // variable is undefined or null
}

Boolean

A Boolean is a logical data type that can have only the values true or false.

Truthy value and Falsy value:

  • JavaScript uses type conversion to coerce any value to a Boolean in Boolean contexts (like conditionals and loops).
  • All values are truthy unless they are defined as falsy which are listed below.

All the values considered to be false

All the values that considered to be false when encountered in a Boolean context like if condition (See Falsy value):

  • false The keyword false.
  • 0 The Number zero (so, also 0.0, etc., and 0x0).
  • -0 The Number negative zero (so, also -0.0, etc., and -0x0).
  • 0n The BigInt zero (so, also 0x0n). Note that there is no BigInt negative zero — the negation of 0n is 0n.
  • "", '' , “` ` Empty string value.
  • null null
  • undefined undefined
  • NaN NaN
  • document.all Objects are falsy if and only if they have the [[IsHTMLDDA]] internal slot.That slot only exists in document.all and cannot be set using JavaScript.

The logical AND &amp;&amp; operator

Note the logical AND (&&) returns the value of the first falsy operand encountered when evaluating from left to right, or the value of the last operand if they are all truthy.

Examples

console.log(1 &amp;&amp; 0 &amp;&amp; 'abc')     // 0
console.log(false &amp;&amp; 1)          // false
console.log(true &amp;&amp; 'abc')       // 'abc'

Convert a Boolean to an Boolean object

The Boolean object is an object wrapper for a boolean value.

Examples

var x = new Boolean(false);
if (x) { // x is an object not a boolean primitive
  // this code is executed
}

x = false
if (x) {
  // this code is not executed
}

// Do not use a Boolean object to convert a non-boolean value to a boolean value.
x = Boolean(expression);     // use this...
x = !!(expression);          // ...or this
x = new Boolean(expression); // don't use this!

Boolean object:

The value passed as the first parameter is converted to a boolean value, if necessary. If the value is omitted or is 0, -0, null, false, NaN, undefined, or the empty string (""), the object has an initial value of false. All other values, including any object, an empty array ([]), or the string “false“, create an object with an initial value of true.

Number

Number type:

The Number type is a double-precision 64-bit binary format IEEE 754 value (numbers between -(2^53 − 1) and 2^53 − 1). In addition to representing floating-point numbers, the number type has three symbolic values: +Infinity, -Infinity, and NaN (“Not a Number”).

To check for the largest available value or smallest available value within ±Infinity, you can use the constants Number.MAX_VALUE or Number.MIN_VALUE.

Examples

// 0 is represented as both -0 and +0. (0 is an alias for +0.)
console.log(+0 === -0);    // true

+Infinity, -Infinity, and NaN

// Infinity and -Infinity
console.log(42 / +0); // Infinity
console.log(42 / -0); // -Infinity

// NaN
console.log(NaN); // NaN

// Use isNaN() to determineswhether a value is NaN or not.
console.log(isNaN('100F'));      // true
console.log(isNaN(1.23));        // false
console.log(isNaN('3.14'));      // false
console.log(isNaN('0.0314E+2')); // false

Starting with ECMAScript 2015, you are also able to check if a number is in the double-precision floating-point number range.

// Check if a number is in the double-precision floating-point number range.

// Out of safe range, the precision may be lost.
console.log(Number.isSafeInteger(Math.pow(2, 53))); // false
// In safe range, the precision is safe.
console.log(Number.isSafeInteger(Math.pow(2, 53) - 1)); // true
// The max safe inteer and the min safe integer.
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
console.log(Number.MIN_SAFE_INTEGER); // -9007199254740991

Resource

JavaScript Map

This article illustrates the operations of JavaScript Map. Most of the content come from MDN.

About Map

The Map object holds key-value pairs and remembers the original insertion order of the keys. It iterates its elements in insertion order — a for...of loop returns an array of [key, value] for each iteration.

  • Key equality

    NaN is considered the same as NaN (even though NaN !== NaN) and all other values are considered equal according to the semantics of the === operator.

    let myMap = new Map()
    myMap.set(NaN, 'not a number')
    
    myMap.get(NaN)
    // "not a number"
    
    let otherNaN = Number('foo')
    myMap.get(otherNaN)
    // "not a number"
    

Basic operations

Create

Use the Map() constructor creates Map objects.

Syntax:

new Map([iterable]);
// iterable can be an Array or other iterable objects whose elements are key-value pairs.

Examples:

let map1 = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);

let map2 = new Map();
map2.set(1, 'one');
map2.set(2, 'two');
map2.set(3, 'three');

Size

console.log(map1.size);

Loop

There are several ways to loop a Map object.

iterator or entries

The initial value of the @@iterator property is the same function object as the initial value of the entries method. The map iterator function, which is the entries() function by default.

iterator:

const map1 = new Map();

map1.set('0', 'foo');
map1.set(1, 'bar');

const iterator1 = map1[Symbol.iterator]();

for (const item of iterator1) {
    console.log(item);
}
// expected output: Array ["0", "foo"]
// expected output: Array [1, "bar"]

entries():

The entries() method returns a new Iterator object that contains the [key, value] pairs for each element in the Map object in insertion order. In this particular case, this iterator object is also an iterable, so the for-of loop can be used.

const iterator1 = map1.entries();
for (const item of iterator1) {
    console.log(item);
}
// expected output: Array ["0", "foo"]
// expected output: Array [1, "bar"]

for…of

let myMap = new Map();
myMap.set(0, 'zero');
myMap.set(1, 'one');

// -------- Loop key, value pairs
for (let [key, value] of myMap) {
  console.log(key + ' = ' + value)
}
// 0 = zero
// 1 = one

// -------- Loop keys
for (let key of myMap.keys()) {
  console.log(key)
}
// 0
// 1

// -------- Loop values
for (let value of myMap.values()) {
  console.log(value)
}
// zero
// one

// -------- entries
for (let [key, value] of myMap.entries()) {
  console.log(key + ' = ' + value)
}
// 0 = zero
// 1 = one

forEach

The forEach() method executes a provided function once per each key/value pair in the Map object, in insertion order.

function logMapElements(value, key, map) {
  console.log(`m[${key}] = ${value}`);
}

new Map([['foo', 3], ['bar', {}], ['baz', undefined]])
  .forEach(logMapElements);

// expected output: "m[foo] = 3"
// expected output: "m[bar] = [object Object]"
// expected output: "m[baz] = undefined"

Syntax

myMap.forEach(callback([value][, key][, map])[, thisArg])

Parameters

  • callback

    Function to execute for each entry of myMap. It takes the following arguments:

    • value Optional. Value of each iteration.
    • key Optional. Key of each iteration.
    • map Optional. The map being iterated (myMap in the above Syntax box).
  • thisArg Optional

    Value to use as this when executing callback.

Delete

  • delete(), deletes the specified element.
  • clear(), deletes all elements.

delete()

The delete(key) method removes the specified element from a Map object by key.

It returns true if an element in the Map object existed and has been removed, or false if the element does not exist.

const map1 = new Map();
map1.set('bar', 'foo');

console.log(map1.delete('bar'));
// expected result: true
// (true indicates successful removal)

console.log(map1.has('bar')); // false

clear()

The clear() method removes all elements from a Map object.

const map1 = new Map();

map1.set('bar', 'baz');
map1.set(1, 'foo');
console.log(map1.size); // 2

map1.clear();
console.log(map1.size); // 0

get()

The get(key) method returns a specified element from a Map object. If the value that is associated to the provided key is an object, then you will get a reference to that object and any change made to that object will effectively modify it inside the Map object.

const map1 = new Map();
map1.set('bar', 'foo');

console.log(map1.get('bar')); // "foo"
console.log(map1.get('baz')); // undefined

has()

The has(key) method returns a boolean indicating whether an element with the specified key exists or not.

const map1 = new Map();
map1.set('bar', 'foo');

console.log(map1.has('bar')); // true
console.log(map1.has('baz')); // false

set()

The set(key, value) method adds or updates an element with a specified key and a value to a Map object.

const map1 = new Map();

map1.set('bar', 'foo');
console.log(map1.get('bar')); // "foo"

map1.set('bar', 'foo2');
console.log(map1.get('bar')); // "foo2"

This is the only right way to update or add an element through set().

Note

It is possible to set properties of a Map like other generic objects, but this will cause considerable confusion. For these properties set in this way are not stored in the Map for queries. Other operations on the data fail:

let wrongMap = new Map()
wrongMap['bla'] = 'blaa'
wrongMap['bla2'] = 'blaaa2'

console.log(wrongMap)  // Map { bla: 'blaa', bla2: 'blaaa2' }

// Other operations on the data fail.

wrongMap.has('bla')    // false
wrongMap.delete('bla') // false
console.log(wrongMap)  // Map { bla: 'blaa', bla2: 'blaaa2' }

Members

entities()

The entries() method returns a new Iterator object that contains the [key, value] pairs for each element in the Map object in insertion order. In this particular case, this iterator object is also an iterable, so the for-of loop can be used. When the protocol [Symbol.iterator] is used, it returns a function that, when invoked, returns this iterator itself.

const map1 = new Map();

map1.set('0', 'foo');
map1.set(1, 'bar');

const iterator1 = map1.entries();

console.log(iterator1.next().value);
// expected output: ["0", "foo"]

console.log(iterator1.next().value);
// expected output: [1, "bar"]

keys()

The keys() method returns a new Iterator object that contains the keys for each element in the Map object in insertion order.

const map1 = new Map();

map1.set('0', 'foo');
map1.set(1, 'bar');

const iterator1 = map1.keys();

console.log(iterator1.next().value);
// expected output: "0"

console.log(iterator1.next().value);
// expected output: 1

values()

The values() method returns a new Iterator object that contains the values for each element in the Map object in insertion order.

const map1 = new Map();

map1.set('0', 'foo');
map1.set(1, 'bar');

const iterator1 = map1.values();

console.log(iterator1.next().value);
// expected output: "foo"

console.log(iterator1.next().value);
// expected output: "bar"

Other operations

Map and Array

Array to Map

It is easy to convert an Array to a Map, for Map() constructor accepts an array as its parameter:

let kvArray = [['key1', 'value1'], ['key2', 'value2']];

// Use the regular Map constructor to transform a 2D key-value Array into a map
let myMap = new Map(kvArray);

Map to Array

To convert a Map to an Array:

let kvArray = [['key1', 'value1'], ['key2', 'value2']];
let myMap = new Map(kvArray);

// Use Array.from() to transform a map into a 2D key-value Array
let arr1 = Array.from(myMap);
console.log(arr1); // Will show you exactly the same Array as kvArray

// A succinct way to do the same, using the spread syntax
let arr2 = [...myMap];
console.log(arr2);

let arr3 = Array.from(myMap.keys());
console.log(arr3); // ["key1", "key2"]

Map and JSON

There is not direct support to convert a map to JSON, we can do that by using array.

Map to Json

let jsonStr = JSON.stringify([...myMap]);
// Or
let jsonStr2 = JSON.stringify(Array.from(myMap));

JSON to Map

let myMap = Map(JSON.parse(jsonStr));

Object vs Map

An object is similar to a map, both of them have a key-value structure. Objects vs Maps lists the differences between objects and a maps. Below are some main of them :

  • Key type:
    • The keys of an Object must be either a String or a Symbol.
    • A Map‘s keys can be any value (including functions, objects, or any primitive).
  • Size:
    • You need to get the item count of an object manually.
    • Use size property to directly get size of a map.
  • Iteration
    • By default, Object does not implement an iteration protocol, it is not directly iterable using for...of statement (by default). The for…in statement allows you to iterate over the enumerable properties of an object.
    • A Map is iterable, it can be directly iterated.
  • Performance

    When it comes to scenarios involving frequent additions and removal:

    • A map performs better.

Suggestions about object vs map

geeksforgeeks: Map vs Object:

Object is much more than a Map, it shouldn’t be used just for the purpose of hashing if there exists another choice.

Reference

JavaScript String

This article lists the operations of JavaScript String and the things you want to know about it. Treat it as a reference list.

About String in JavaScript

As in other programming languages, a string in JavaScript is also a sequence of characters.

A Sting is one of the seven primitive data types in JavaScript and String object is a wrapper for a String primitive.

A set of 16-bit unsigned integer values

It is a set of “elements” of 16-bit unsigned integer values.

Immutable

Unlike some programming languages (such as C), JavaScript strings are immutable. This means that once a string is created, it is not possible to modify it.

String primitives and String objects

JavaScript automatically converts primitives to String objects, so that it’s possible to use String object methods for primitive strings. In contexts where a method is to be invoked on a primitive string or a property lookup occurs, JavaScript will automatically wrap the string primitive and call the method or perform the property lookup.

let s_prim = 'foo'
let s_obj = new String(s_prim)

console.log(typeof s_prim) // Logs "string"
console.log(typeof s_obj)  // Logs "object"

String primitives and String objects also give different results when using eval(). Primitives passed to eval are treated as source code; String objects are treated as all other objects are, by returning the object. For example:

let s1 = '2 + 2'              // creates a string primitive
let s2 = new String('2 + 2')  // creates a String object
console.log(eval(s1))         // returns the number 4
console.log(eval(s2))         // returns the string "2 + 2"

A String object can always be converted to its primitive counterpart with the valueOf() method.

console.log(eval(s2.valueOf()))  // returns the number 4

Escape notation

Code Output
U+0000 NULL character
' single quote
" double quote
\ backslash
n new line
r carriage return
v vertical tab
t tab
b backspace
f form feed
uXXXX (where XXXX is 4 hex digits; range of 0x00000xFFFF) UTF-16 code unit / Unicode code point between U+0000 and U+FFFF
u{X}u{XXXXXX} (where X…XXXXXX is 1–6 hex digits; range of 0x00x10FFFF) UTF-32 code unit / Unicode code point between U+0000 and U+10FFFF
xXX (where XX is 2 hex digits; range of 0x000xFF) ISO-8859-1 character / Unicode code point between U+0000 and U+00FF

Basic operations

Create strings

Strings can be created as primitives from string literals, or as object using String() constructor. In most situations, they can be used interchangeably.

const str1 = 'Hello';
const str2 = "World";
const str3 = `A template string`;
const str4 = new String('A string object');

A string literal can be enclosed by single quotes '' or double quotes"", both of them are treated identically. Or you can use a pair of backticks ““ to create a template string (or a template literal).

Template literals (Template strings)

Template literals can be useful, below are 2 of the examples:

They can contain placeholders:

let a = 5;
let b = 10;
console.log(`a is ${a}, b is ${b}`); // a is 5, b is 10

console.log(`a * 10 times: ${a.repeat(10)}`); // a * 10 times: aaaaaaaaaa

They can be used to represent multiple lines in a direct way:

console.log(`string text line 1
string text line 2`);

Multiple line of strings

There are several ways to represent multiple line of string or long strings in JavaScript.

1.Use + to append multiple strings together:

let longString = "This is a very long string which needs " +
                 "to wrap across multiple lines because " +
                 "otherwise my code is unreadable.";

2.Use the backslash character (“) at the end of each line to indicate that the string will continue on the next line:

let longString = "This is a very long string which needs 
to wrap across multiple lines because 
otherwise my code is unreadable.";

3.Use a template string:

let longString = `This is a very long string which needs
 to wrap across multiple lines because
 otherwise my code is unreadable.`;

Length

const str1 = 'Hello';
console.log(str1.length); // 5

Access characters or elements

To access an individual character:

return 'cat'.charAt(1); // returns "a"
// Or
return 'cat'[1] // returns "a"

Not it will not succeed to delete or assign a new value via [].

Check

startsWith()

The startsWith(searchString[, position] method determines whether a string begins with the characters of a specified string, returning true or false as appropriate.

  • position, the position in this string at which to begin searching for searchString. Defaults to 0.
const str1 = 'Hello!';

console.log(str1.startsWith('He'));    // true
console.log(str1.startsWith('lo', 3)); // true

console.log(str1.startsWith('A'));     // false

endsWith()

The endsWith(searchString[, length]) method determines whether a string ends with the characters of a specified string, returning true or false as appropriate.

  • length, it is used as the length of str. Defaults to str.length.
const str1 = 'Hello!';

console.log(str1.endsWith('llo'));    // true
console.log(str1.endsWith('llo', 3)); // false

console.log(str1.endsWith('?'));      // false

Compare

Use operators to compare two strings.

const a = 'a'
const b = 'b'

console.log( a < b);  // true
console.log( a <= b); // true
console.log( a > b);  // false

Equal check:

  • == return true if the operands are equal.
  • === returns true if the operands are equal and of the same type.
const a = 'Hello';
const b = new String('Hello');
const c = 'hello';

const d = 1;
const f = '1';

//------------- equal: ==

// Compare the strings in a and b for being equal in the usual case-sensitive way.
console.log(a == b); // true
console.log(a == c); // false
console.log(d == f); // true

// Case-insentitive
console.log(c.toUpperCase() == c.toUpperCase()); // true

// ------------ strict equal: ===

console.log(a === b); // false

Concat

+

The concatenation operator (+) concatenates two string values together, returning another string that is the union of the two operand strings.

Examples

// +

let a = 'hello';
console.log(a + ' world'); // 'hello world'

console.log(123 + a); // '123hello'

// +=
// The shorthand assignment operator += can also be used to concatenate strings.

a += ' world';
console.log(a); // 'hello world'

contact()

The concat() method concatenates the string arguments to the calling string and returns a new string.

Examples

// To concatenate two or more strings.

const str1 = 'Hello';
const str2 = 'World';

console.log(str1.concat(' ', str2));
// expected output: "Hello World"

console.log(str1.concat(' ', str2, ', ', str1, ' ', str2));
// expected output: "Hello World, Hello World"

Index of

indexOf()

indexOf(searchValue[, fromIndex]) returns the index within the calling String object of the first occurrence of the specified value, starting the search at fromIndex. Returns -1 if the value is not found.

lastIndexOf()

The lastIndexOf(searchValue[, fromIndex]) method returns the index within the calling String object of the last occurrence of the specified value, searching backwards from fromIndex. Returns -1 if the value is not found.

Lower, upper

toLowerCase()

The toLowerCase() method returns the calling string value converted to lower case.

const sentence = 'The quick brown fox jumps over the lazy dog.';

console.log(sentence.toLowerCase());
// expected output: "the quick brown fox jumps over the lazy dog."

toUpperCase()

The toUpperCase() method returns the calling string value converted to uppercase (the value will be converted to a string if it isn’t one).

const sentence = 'The quick brown fox jumps over the lazy dog.';

console.log(sentence.toUpperCase());
// expected output: "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG."

Pad

padEnd()

The padEnd(targetLength [, padString) method pads the current string with a given string (repeated, if needed) so that the resulting string reaches a given length. The padding is applied from the end of the current string.

  • padString, if it is too long to stay within targetLength, it will be truncated. Default value is " ".
const str1 = 'Breaded Mushrooms';

console.log(str1.padEnd(25, '.'));
// expected output: "Breaded Mushrooms........"

const str2 = '200';

console.log(str2.padEnd(5));
// expected output: "200  "

padStart()

The padStart(targetLength [, padString]) method pads the current string with another string (multiple times, if needed) until the resulting string reaches the given length. The padding is applied from the start of the current string.

const str1 = '5';

console.log(str1.padStart(2, '0'));
// expected output: "05"

const fullNumber = '2034399002125581';
const last4Digits = fullNumber.slice(-4);
const maskedNumber = last4Digits.padStart(fullNumber.length, '*');

console.log(maskedNumber);
// expected output: "************5581"

Repeat

repeat()

To make a string repeat multiple times:

const a = 'a';

console.log(chorus.repeat(10));
// expected output: "aaaaaaaaaa"

Replace

The replace() and replaceAll return a new string with some or all matches of a pattern replaced by a replacement. The pattern can be a string or a RegExp, and the replacement can be a string or a function to be called for each match.

For replace(), if pattern is a string, only the first occurrence will be replaced.

replace()

The replace() method returns a new string with some or all matches of a pattern replaced by a replacement.

Syntax

replace(regexp|substr, newSubstr|function)

Examples

const p = 'The quick brown fox jumps over the lazy dog. If the dog reacted, was it really lazy?';

console.log(p.replace('dog', 'monkey'));
// expected output: "The quick brown fox jumps over the lazy monkey. If the dog reacted, was it really lazy?"


const regex = /Dog/i;
console.log(p.replace(regex, 'ferret'));
// expected output: "The quick brown fox jumps over the lazy ferret. If the dog reacted, was it really lazy?"

replaceAll()

The replaceAll() method returns a new string with all matches of a pattern replaced by a replacement.

Syntax

replaceAll(regexp|substr, newSubstr|function)

Examples

const p = 'The quick brown fox jumps over the lazy dog. If the dog reacted, was it really lazy?';

console.log(p.replaceAll('dog', 'monkey'));
// expected output: "The quick brown fox jumps over the lazy monkey. If the monkey reacted, was it really lazy?"


// global flag required when calling replaceAll with regex
const regex = /Dog/ig;
console.log(p.replaceAll(regex, 'ferret'));
// expected output: "The quick brown fox jumps over the lazy ferret. If the ferret reacted, was it really lazy?"

Sub string

There are several methods to extract a port of a string, substring and slice are almost identical except some subtle differences if start is greater than end, or there are negative arguments:

  • substring(start[, end]), if start is greater than end, substring swaps the two argument.
  • slice(begin[, end]), if begin is greater than (or equal) end, it returns an empty string. If end is negative, it is treated as str.length + endIndex.
  • substr(start[, length]), a legacy feature in ECMAScript. It’s best to avoid using it if possible.

Differences between substring() and slice():

const str = 'Hello';

// If start is greater than end.
console.log(str.substring(5, 2));  // "llo"
console.log(str.slice(5, 2));      // ""

// Negative values.

// Negative start is treated as 0 for substring().
console.log(str.substring(-3, 3));  // "Hel"
// Negative start is treated as str.length -start for slice().
console.log(str.slice(-3, 3));      // "l"

// Negative end is treated as 0;
console.log(str.substring(0, -2)); // ""
// Negative end is treated as str.length + endIndex for slice().
console.log(str.slice(0, -2));     // "Hel"

substring()

The substring(start[, end]) returns the part of the string between the start and end indexes, or to the end of the string.

const str = 'Hello';

console.log(str.substring(1, 3)); // "el"
console.log(str.substring(2));    // "llo"
console.log(str.substring(0, 100)); // "Hello"

// Negative argument is treated as 0.
console.log(str.substring(-3));     // "Hello"
console.log(str.substring(-3, 3));  // "Hel"

// If start is greater than end, the effect of substring() is
// as if the two arguments were swapped.
console.log(100, 1);   // "ello"
console.log(100, 10);  // ""
console.log(100, 200); // ""

// NaN is treated as 0.
console.log(str.substring('a', 0));  // "H"
console.log(str.substring(0, 'a')); // ""

slice()

const str = 'Hello';

console.log(str.slice(1, 3));   // "el"
console.log(str.slice(2));      // "llo"
console.log(str.slice(0, 100)); // "Hello"

console.log(str.slice(-3));     // "llo"
console.log(str.slice(-3, 3));  // "l"

// NaN is treated as 0.
console.log(str.slice('a', 0));  // "H"
console.log(str.slice(0, 'a')); // ""

substr() (a legacy feature)

substr(start[, length]) returns a portion of the string, starting from the start index and extending for characters of length or ones to the end if length is not specified.

const str = 'Hello';

console.log(str.substr(1, 2)); // "el"
console.log(str.substr(2));    // "llo"

console.log(str.substr(-3));     // "llo"
console.log(str.substr(-3, 2));  // "ll"

// The value of start is capped at str.length if its value is out of range.
console.log(str.substr(-100, 2));// "He"

// If length is negative, it is reated as 0
console.log(str.substr(-3, -2)); // ""
console.log(str.substr(1, -2));  // ""

Search

includes()

The includes(searchString[, position]) method performs a case-sensitive search to determine whether one string may be found within another string, returning true or false as appropriate.

  • position, the position within the string at which to begin searching for searchString. (Defaults to 0.)
const sentence = 'The quick brown fox jumps over the lazy dog.';
const word = 'fox';

console.log(sentence.includes(word)); // true
console.log(sentence.includes(word, 20)); // false

search()

The search(regexp) method executes a search for a match between a regular expression and this String object.

If a non-RegExp object regexp is passed, it is implicitly converted to a RegExp with new RegExp(regexp).

const paragraph = 'The quick brown fox jumps over the lazy dog. If the dog barked, was it really lazy?';

// any character that is not a word character or whitespace
const regex = /[^ws]/g;

console.log(paragraph.search(regex));
// expected output: 43

console.log(paragraph[paragraph.search(regex)]);
// expected output: "."

Split

split()

The split([separator[, limit]) method divides a String into an ordered list of substrings, puts these substrings into an array, and returns the array.

  • separator, the separator can be a simple string or it can be a regular expression.
  • limit, the number of substrings to be included in the array. If it is 0, [] is returned.
const str = 'Welcome to JavaScript world.';

const words = str.split(' '); // words: ["Welcome","to","JavaScript","world."]

const chars = str.split('');
// chars: ["W","e","l","c","o","m","e"," ","t","o"," ","J","a","v","a","S","c","r","i","p","t"," ","w","o","r","l","d","."]

const strCopy = str.split(); // strCopy: ["Welcome to JavaScript world."]

To string

toString()

The toString() method returns a string representing the specified object.

const stringObj = new String('foo');

console.log(stringObj);
// expected output: String { "foo" }

console.log(stringObj.toString());
// expected output: "foo"

Trim

To remove whitespace from both or either of the ends of a string. Whitespace in this context is all the whitespace characters (space, tab, no-break space, etc.) and all the line terminator characters (LF, CR, etc.).

const greeting = '   Hello world!   ';

console.log(greeting);             // expected output: "   Hello world!   ";

// To remove whitespace from both ends.
console.log(greeting.trim());      // expected output: "Hello world!";

// To remove whitespace from the beginning
console.log(greeting.trimStart()); // expected output: "Hello world!

// To remove whitespace from the end
console.log(greeting.trimEnd());   // expected output: "   Hello world!";

valueOf

valueOf()

The valueOf() method returns the primitive value of a String object.

const stringObj = new String('foo');

console.log(stringObj);
// expected output: String { "foo" }

console.log(stringObj.valueOf());
// expected output: "foo"

Char ↔ Unicode

Unicode to char

The static String.fromCharCode() method returns a string created from the specified sequence of UTF-16 code units.

console.log(String.fromCharCode(189, 43, 190, 61));
// expected output: "½+¾="

It accept a sequence of numbers that are UTF-16 code units. The range is between 0 and 65535 (0xFFFF). Numbers greater than 0xFFFF are truncated. No validity checks are performed.

Char to Unicode

The charCodeAt() method returns an integer between 0 and 65535 representing the UTF-16 code unit at the given index.

const sentence = 'The quick brown fox jumps over the lazy dog.';

const index = 4;

console.log(`The character code ${sentence.charCodeAt(index)} is equal to ${sentence.charAt(index)}`);
// expected output: "The character code 113 is equal to q"

string ↔ number

String to number

// string to integer

console.log(Number.parseInt('')); // NaN

console.log(Number.parseInt('15')); // 15
console.log(Number.parseInt('015')); // 15
console.log(Number.parseInt(' 15')); // 15

console.log(Number.parseInt('10', 2)); // 2
console.log(Number.parseInt('100', 2)); // 4
console.log(Number.parseInt('15', 2)); // 1

console.log(Number.parseInt('15', 8)); // 13
console.log(Number.parseInt(' 0xF', 16)); // 15

// Use Number.isNaN() to check whether the returned value is NaN.
let n1 = Number.parseInt('a');
console.log(Number.isNaN(n1)); // true

// string to float
console.log(Number.parseFloat('3.14abc')); // 3.14
console.log(Number.parseFloat('abc3.14')); // NaN

Number.parseInt vs parseInt

Number.parseInt === parseInt // true
Number.parseFloat === parseFloat; // true

Number to string

The toString() method returns a string representing the specified Number object.

let count = 10

console.log(count.toString())    // '10'
console.log((17).toString())     // '17'
console.log((17.2).toString())   // '17.2'

let x = 6

console.log(x.toString(2))       // '110'
console.log((254).toString(16))  // 'fe'

console.log((-10).toString(2))   // '-1010'
console.log((-0xff).toString(2)) // '-11111111'

Reference

JavaScript Array Operations with Examples

This article illustrates the operations of JavaScript Array. These examples include built-in APIs and some custom functions, such as groupBy, union, objectToArray, etc.

Note if fruits is not initialized in examples, it is:

let fruits = ['Apple', 'Banana'];

Basic operations

Create

let fruits = ['Apple', 'Banana'];
let length = fruits.length;
// 2

let fruits1 = [];
fruits1.push('Apple'); // ['Apple']
fruits1.push('Banada');// ['Apple', 'Banana'];

let fruits2 = new Array('Apple', 'Banana');

// Creates an array of 2 empty slots, not slots with actual undefined values
let fruit3 = new Array(2);
console.log(fruits3);
// Array [undefined, undefined]

Create an two-dimensional array

let board = [
    ['A0','A1','A2'],
    ['B0','B1','B2'],
    ['C0','C1','C2']
];

Set values

Set values directly

let fruits = ['Apple', 'Banana'];
fruits[1] = 'Blackberry';

If you set a value at a new indexed position which is not contained in the array. The length of the array will be updated accordingly.

let fruits = ['Apple', 'Banana'];
fruits[4] = 'Damson';
console.log(fruits); // ['Apple', 'Banana', undefined, undefined, 'Damson'];
console.log(furits.length); // 5
console.log(Object.keys(fruits));  // ['0', '1', '4']

**Fill values **

The fill() method fills (modifies) all the elements of an array from a start index (default zero) to an end
index (default array length) with a static value. It returns the modified array.

Syntax: fill(value[, start[, end]])

let array1 = [1, 2, 3, 4];

// fill with 0 from position 2 until position 4
console.log(array1.fill(0, 2, 4));
// expected output: [1, 2, 0, 0]

// fill with 5 from position 1
console.log(array1.fill(5, 1));
// expected output: [1, 5, 5, 5]

console.log(array1.fill(6));
// expected output: [6, 6, 6, 6]

Access

length

console.log(fruits.length);

[] operator

Access an element through index

let first = fruits[0];
// Apple

let last = fruits[fruits.length - 1];
// Banana

Note: It is possible to quote the array indexes, such as fruits['0'], although it is not necessary. The 0 in fruits[0] is coerced into a string by the JavaScript engine through an implicit toString conversion.

Add, change

push() – Add to the end

// push() changes the length and returns the new length
let newLength = fruits.push('Coconut'); // ["Apple", "Banana", "Coconut"]
// 3

unshift() – Add to the front

// unshift() changes the length and returns the new length
let newLength = fruits.unshift('Avocado');// ["Avocado", "Apple", "Banana"];
// 3

[] operator – Add by setting a value at a new index position

let fruits = ['Apple', 'Banana'];
fruits[4] = 'Damson';
console.log(fruits); // ['Apple', 'Banana', undefined, undefined, 'Damson'];
console.log(furits.length); // 5
console.log(Object.keys(fruits));  // ['0', '1', '4']

When setting a property on a JavaScript array when the property is an valid array index and that index is outside the current bounds of the array, the engine will update the array’s length property accordingly.

splice() – Add value of a specific position

The splice() method changes the contents of an array by removing or replacing existing elements and/or adding new elements in place. It returns an array containing the deleted elements.

let arrDeletedItems = arr.splice(start[, deleteCount[, item1[, item2[, ...]]]])
  • start

    The index at which to start changing the array.

  • deleteCount

    The number of elements in the array to remove from start.

  • item1, item2, ...

    The elements to add to the array, beginning from start. If you do not specify any elements, splice() will only remove elements from the array.

Example

let fruits = ['Apple', 'Banada', 'Coconut'];

// Remove 0 item from index of 1, and adding new item 'Blueberry'
let deletedItems = fruits.splice(1, 0, 'Blueberry'); // Add at index
// deletedItems: []
// fruits: ['Apple', 'Blueberry', Banada', 'Coconut']

Delete, remove

Delete from the end of an array

The pop() method removes the last element from an array and returns that element. This method changes the length of the array.

It returns undefined if the array is empty.

// pop() returns the removed element, it changes the length
// remove Banada from the end
let last = fruits.pop(); // ["Apple"];
// Banada

Delete from the front of an array

// shift() returns the removed element, it changes the length
// remove Apple from the front
let first = fruits.shift(); // ["Banana"];
// Apple

Delete elements from any position of an array

The splice() method changes the contents of an array by removing or replacing existing elements and/or adding new elements in place. It can be used to remove elements from any position.

let fruits = ['Apple', 'Banada', 'Coconut', 'Durian'];

// Delete fruits[1] and fruits[2]
let removed = fruits.splice(1, 2); // fruits: ['Apple', 'Durian']
console.log(removed); // ['Banada', 'Coconut']

Loop

There are several ways to loop over an array. Use mapreducefilter functions first if they satisfy your needs, otherwise use below loop operations. Although these function are slower, they are easier to code、read , maintain and test. See for vs for each vs (map, reduce, filter, find) for details.

Use for loop

for (let i = 0; i < furits.length; ++i) {
    console.log(furits[i]);
}

Use forEach method

The forEach() method executes a provided callback function once for each array element.

// The parameters of index and array are optional
fruits.forEach(function(currenValue, index, array) {
    console.log(curentValue, index);
});
// "Apple" 0
// "Banada" 1

Note: It is not invoked for index properties that have been deleted or are uninitialized. For example:

“`javascript
const arraySparse = [1,3,,7];
let numCallbackRuns = 0;

arraySparse.forEach(function (element) {
console.log(element);
numCallbackRuns++;
});
console.log(arraySparse); // 3
“`

Formatted print

Use console.table

console.table displays tabular data as a table. This feature is available in Web Workers.

console.table(['Apple', 'Banada']);
console.table(board); // prints a two-demensional array

Write your own function

function printArrayElements(element, index, array) {
    console.log('a[' + index + '] = ' + element);
}

fruits.forEach(printArrayElements);
// a[0] = "Apple"
// a[1] = "Banada"

Other Operations

Copy

Copy to a new array

The slice(begin, end) method returns a shallow copy of a portion of an array into a new array object (end not included) . The original array will not be modified.

let fruits = ['Apple', 'Banada'];
let shallowCopy = fruits.slice(); // this is how to make a copy
// ["Apple", "Banada"]

Syntax: slice([begin[, end]]).

Copy elements to the same array

The copyWithin(target, start, end) method shallow copies part of an array to target location in the same array and returns it without modifying its length.

Syntax: copyWithin(target[, start[, end]]).

let array1 = ['a', 'b', 'c', 'd', 'e'];

// copy to index 0 the element at index 3
console.log(array1.copyWithin(0, 3, 4));
// expected output: Array ["d", "b", "c", "d", "e"]

// copy to index 1 all elements from index 3 to the end
console.log(array1.copyWithin(1, 3));
// expected output: Array ["d", "d", "e", "d", "e"]

Filter

The filter() method creates a new array with all elements that pass the test implemented by the provided function.

let fruits = ['Apple', 'Banana', 'Blueberry'];
let bfruits = fruits.filter(val => val.startsWith('B'));
// ['Banana', 'Blueberray']

Find

findIndex()

The findIndex() method returns the index of the first element in the array that satisfies the provided testing function.

Syntax

arr.findIndex(callback( element[, index[, array]] )[, thisArg])

Parameters

  • callback

    A function to execute on each value in the array until the function returns true, indicating that the satisfying element was found.

    It takes three arguments:

    • element

    The current element being processed in the array.

    • index Optional

    The index of the current element being processed in the array.

    • array Optional

    The array findIndex() was called upon.

  • thisArg Optional

    Optional object to use as this when executing callback.

Examples

let furits = ['Apple', 'Banana'];

function startsWithB(element) {
   return element.startsWith('B');
}

console.log(fruits.findIndex(startsWithB));
// expected output: 1

find()

The find() method returns the value of the first element in the array that satisfies the provided testing function. Otherwise undefined is returned.

let array1 = [5, 12, 8, 130, 44];

let elemnent = array1.find(function(element) {
    return element > 10;
});

console.log(element);
// 12

indexOf()

Get index of an element

The indexOf() method returns the first index at which a given element can be found in the array, or -1 if it is not present.

Syntax

arr.indexOf(searchElement[, fromIndex])

Parameters

  • searchElement

    Element to locate in the array.

  • fromIndex Optional

    The index to start the search at. If the index is greater than or equal to the array’s length, -1 is returned, which means the array will not be searched. If the provided index value is a negative number, it is taken as the offset from the end of the array. Note: if the provided index is negative, the array is still searched from front to back. If the provided index is 0, then the whole array will be searched. Default: 0 (entire array is searched).

indexOf() compares searchElement to elements of the Array using strict equality (the same method used by the === or triple-equals operator).

Examples

let array = [2, 9, 9];
array.indexOf(2);     // 0
array.indexOf(7);     // -1
array.indexOf(9, 2);  // 2
array.indexOf(2, -1); // -1
array.indexOf(2, -3); // 0

includes()

Check whether an array includes a certain value

The includes() method determines whether an array includes a certain value among its entries, returning true or false as appropriate.

Syntax: includes(valueToFind[, fromIndex])

let fruits = ['Apple', 'Banana'];
console.log(fruits.includes('Orange'));// false
console.log(fruits.includes('App'));// false

Note: When comparing strings and characters, includes() is case-sensitive.

Note: Technically speaking, includes() uses the sameValueZero algorithm to determine whether the given element is found.

Join

The join(sperator) method creates and returns a new string by concatenating all of the elements in an array (or an array-like object), separated by commas or a specified separator string

let fruits = ['Apple', 'Banada'];

console.log(fruits.join());
// expected output: "Apple,Banada"

console.log(elements.join('-'));
// expected output: "Apple-Banada"

let board = [
  ['A','B','C'],
  ['D','E','F'],
  ['G','H','I']
];
console.log(board.join('n'));
// Output:
/*
A,B,C
D,E,F
G,H,I
*/

Merge / Concat

The concat()method is used to merge two or more arrays. This method does not change the existing arrays, but instead returns a new array.

let array1 = ['a', 'b', 'c'];
let array2 = ['d', 'e', 'f'];

console.log(array1.concat(array2));
// Array ["a", "b", "c", "d", "e", "f"]

Sort

Reverse

The reverse() method reverses an array in place. The first array element becomes the last, and the last array element becomes the first.

let array1 = ['one', 'two', 'three'];
let reversed = array1.reverse();
console.log(array1); // Array ['three', 'two', 'one']
console.log(reversed); // Array ['three', 'two', 'one']

Check/Test

Every: Test whether all elements pass provided function

The every() method tests whether all elements in the array pass the test implemented by the provided function.

function isBelowThreshold(currentValue) {
    return currentValue < 40;
}

let array1 = [1, 30, 39, 29, 10, 13];
console.log(array1.every(isBelowThreshold));
// true

Note: This method returns true for any condition put on an empty array.

Some: Test whether at least some elements pass provided function

The some() method tests whether at least one element in the array passes the test implemented by the provided function.

let even = function(element) {
    // checks whether an element is even
    return element % 2 === 0;
};

let array = [1, 2, 3, 4, 5];
console.log(array.some(even));
// true

Note: This method returns false for any condition put on an empty array.

isArray

The Array.isArray() method determines whether the passed value is an Array.

Array.isArray([1, 2, 3]);  // true
Array.isArray({foo: 123}); // false
Array.isArray('foobar');   // false
Array.isArray(undefined);  // false

isArray vs instance of

When checking for Array instance, Array.isArray is preferred over instanceof because it works through iframes.

String to Array, Map to Array, Set to Array

(Array.from('foo'));
// ['f', 'o', 'o']

The Array.from() method creates a new, shallow-copied Array instance from an array-like or iterable object.

Array.from() lets you create Arrays from:

  • array-like objects (objects with a length property and indexed elements) or
  • iterable objects (objects where you can get its elements, such as Map and Set)
const set = new Set(['foo', 'bar', 'baz', 'foo']);
Array.from(set);
// ['foo', 'bar', 'baz']

Other loops: map vs reduce

map

map() method creates a new array with the results of calling a provided function on every element in the calling array.

Syntax:

let new_array = arr.map(function callback(currentValue[, index[, array]]) {
    // Return element for new_array
}[, thisArg]
  • currentValue, the current value in the array
  • index
  • array, the array itself
  • thisArg, Value to use as this when executing callback.

Examples:

[1, 4, 9].map((val) => {
  return val * 2;
});
// It creates a new array: [2, 8, 18]

reduce

The reduce() method executes a callback on each element of the array, resulting in a single output value.

Syntax:

arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])
  • accumulator

    The accumulator accumulates the callback’s return values. It is the accumulated value previously returned in the last invocation of the callback, or initialValue.

  • currentValue, the current element being processed in the array.

  • index, the index of the current element being processed in the array.

  • array, the array reduce() was called upon.

  • initialValue

    A value to use as the first argument to the first call of the callback. If no initialValue is supplied, the first element in the array will be used and skipped. Calling reduce() on an empty array without an initialValue will throw a TypeError.

Examples:

// Get sum of an array
[0, 1, 2, 3, 4].reduce(function(accumulator, currentValue, currentIndex, array) {
  return accumulator + currentValue;
});

Custom operations

Filter an Objects Array by fields

filter is an object which defines the filter condition. If an object in the array has the properties with the same values in filter, it will be filtered out.

const filterArray = (arr, filter) => {
  var data = [];
  for (let obj of arr) {
    let filtered = true;
    for (let p in filter) { // for each property in filter
      if (obj[p] != filter[p]) {
        filtered = false;
        break;
      }
    }

    if (filtered) {
      data.push(obj);
    }
  } // for

  return data;
}

Examples:

let employees = [
    {name: 'Jane', gender: 'Female'},
    {name: 'Jim', gender: 'Male' },
    {name: 'Lily', gender: 'Female'}
};
filterArray(employees, {gender: 'Female'});
// [{name: 'Jane', gender: 'Female'}, {name: 'Lily', gender: 'Female'}]

GroupBy

The following groupBy groups objects in an array by a field.

const groupBy = (arr, field) => {
    return arr.reduce(function (acc, obj) {
        let key = obj[field];
        acc[key] = acc[key] || [];
        acc[key].push(obj);
        return acc;
    }, {});
}

Examples:

let employees = [
  {name: 'Jane', gender: 'Female'},
  {name: 'Jim', gender: 'Male' },
  {name: 'Lily', gender: 'Female'}
];
groupBy(employees, 'gender');
// {
//   'Female': [{name: 'Jane', gender: 'Female'}, {name: 'Lily', gender: 'Female'}],
//   'Male': [{name: 'Jim', gender: 'Male' }]
// }

If you want an array of grouped objects:

const groupBy = (arr, field) => {
    let groups = arr.reduce((acc, obj) => {
        let key = obj[field];
        acc[key] = acc[key] || [];
        acc[key].push(obj);
        return acc;
    }, {});

    return Object.keys(groups).map( group => {
        return groups[group];
    });
}

The result will be:

// [
//   [{name: 'Jane', gender: 'Female'}, {name: 'Lily', gender: 'Female'}],
//   [{name: 'Jim', gender: 'Male' }]
// ]

A more generic version

Below code (from javascript codesnipt) is a more generic version. It accepts an array of basic types or objects. It has 2 processes:

  1. Use map to generate a new array. Here arr is the array, and f can be a function or a field name by which it is grouped.
  2. Use reduce function to produce a object in which a field indicates a group.
const groupBy = (arr, f) =>
    arr.map(typeof f === 'function' ? f : val => val[f]).reduce((acc, val, i) => {
        acc[val] = (acc[val] || []).concat(arr[i]);
        return acc;
    }, {});

Examples:

groupBy([6.1, 4.2, 6.3], Math.floor); // {4: [4.2], 6: [6.1, 6.3]}
groupBy(['one', 'two', 'three'], 'length'); // {3: ['one', 'two'], 5: ['three']}

Of course it can be completed in one loop:

const groupBy = (arr, f) => {
    return arr.reduce((acc, val) => {
        let key = (typeof f === 'function') ? f(val) : val[f]).
        acc[key] = acc[key] || [];
        acc[key].push(val);
        return acc;
    }, {});
}

Union

Union operation returns each item in any of a and b once.

const union = (a, b) => Array.from(new Set([...a, ...b]));

Examples:

union([1, 2, 3], [4, 3, 2]);
// [1,2,3,4]

The union above works only for primitive types. You can provides a comparison function to union other types.

const unionWith = (a, b, comp) =>
    Array.from(new Set([...a, ...b.filter(x => a.findIndex(y => comp(x, y)) === -1)]));

It filters the elements that does not appear in a from b, then makes a merge operation.

Array to Set

let set = new Set(['Apple', 'Banana', 'Apple']);
// ('Apple', 'Banana')

Unique Values in Array

const uniqueValues(arr) => [...new Set(arr)];

shuffle

The modern version implemented in JavaScript of this algorithm (Fisher-Yates Algorithm, from the book The Art of Computer Programming by Donald E. Knuth) goes like:

for(let i = array.length — 1; i > 0; i--){
    const j = Math.floor(Math.random() * i)
    const temp = array[i]
    array[i] = array[j]
    array[j] = temp
}

The Phaser’s shuffle method also uses this algorithm, the source code:

var Shuffle = function (array)
{
    for (var i = array.length - 1; i > 0; i--)
    {
        var j = Math.floor(Math.random() * (i + 1));
        var temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }

    return array;
};

Note

Below are two examples of when a random algorithm is not random enough:

  • Flaw #1: An Off-by-One Error

    random(n) returns a number between 0 and (n-1), therefore it will never swap the 52nd card with itself; the 52nd card can never end up in the 52nd place.

  • Flaw #2: The Shuffle Isn’t Uniform

    It swaps the ith card with a random card selected from the entire deck. A proper algorithm will swap the ith card only with a card in the interval(i, n) so that each element for a random swap only once.

  • Flaw #3: Using a 32-bit Seed

  • Flaw #4: Using the System Clock as a Seed

And words from Robert Sedgewick, author of Algorithms:

"That's a pretty tough thing to have happen if you're implementing online poker. You might want to make sure that if you're advertising that you're doing a random shuffle that you go ahead and do so."—Robert Sedgewick, Professor of Computer Science, Princeton

When Random Isn’t Random Enough: Lessons from an Online Poker Exploit

This example comes from a 1999 popular online poker platform implemented card-shuffling software. The algorithm used in the shuffling the 52 cards not random engouth:

{ Generate a new seed based on the system clock }
randomize;
{ Randomly rearrange each card }
for ctr := 1 to 52 do begin
random_number := random(51)+1;
tmp := card[random_number];
card[random_number] := card[ctr];
card[ctr] := tmp;
end;

How To Correctly Shuffle An Array in JavaScript

Microsoft had this browser ballot screen on browserchoice.eu where Microsoft was supposed to show all the different browser choices to users in random order. Here is the code they used for doing the random shuffle:

array.sort(function (a, b) { return 0.5 — Math.random() })

But the algorithm was far from random. Here’s an interesting statistical analysis on the results of this algorithm.

More this sort function is also very inefficient, for it gives a very inconsistent ordering which could go into an infinite loop or stop after a few exchanges.

Object to Array

This operation convert an object to an array.

let objectToArray = obj => {
    Object.keys(obj).map(val => {
        return obj[val];
    }
}

Use Array as object to store other properties

An array can be used as an object to store other properties.

let fruits = ['Orange', 'Lemon'];
fruits.kind = 'citrus';

Output an two-dimensional array in a table format

Use join()

let board = [
  ['A','B','C'],
  ['D','E','F'],
  ['G','H','I']
];

console.log(board.join('n'));

Output

A,B,C
D,E,F
G,H,I

Use console.table()

let board = [
  ['A','B','C'],
  ['D','E','F'],
  ['G','H','I']
];

console.log(board.join('n'));

Output

(index) 0 1 2
0 “A” “B” “C”
1 “D” “E” “F”
2 “G” “H” “I”

Reference