What steps should I take to implement asynchronous functionality in my code for a Google Chrome Extension?

I am currently working on a Google Chrome extension that needs to gather data from two different servers and send it to another service. I am facing an issue with making the process asynchronous, even though the requests are functioning properly.

After searching for explanations on Google, I only came across basic tutorials involving timeouts. The Product-Server accepts the Ajax request, but I encounter a CORS Error with the Deal-Server. To work around this, I have resorted to using XMLHttpRequest.

document.addEventListener("DOMContentLoaded", function () {

    var createButton = document.getElementById("createButton");
    createButton.addEventListener("click", function () {
        getProducts();
        getDeals();
    }, false)


    function getProducts() {
        var list = [];
        chrome.tabs.getSelected(null, function (tab) {
            var Url = parseDealIdToUrl(tab.url)
                $.get(Url, function (data, status) {
                    const jsonData = JSON.parse(JSON.stringify(data))
                    const productArray = jsonData.data
                    productArray.forEach(product => {
                        productList.push(new Product(product.id, product.deal_id, product.product_id, product.name, product.item_price, product.quantity, product.duration, product.discount_percentage, product.currency, product.sum, product.sum_formatted))
                    });

                })
        });
    }

    function getDeals(maxPages = 1, currentPage = 1, akquises = []) {
        var akquiseList = akquises;

        if (currentPage <= maxPages) {
            var Url = dealUrl + currentPage
            var xhr = new XMLHttpRequest();
            xhr.open("GET", Url, true);
            xhr.setRequestHeader("Authorization", "Token token=")
            xhr.onreadystatechange = function () {
                if (xhr.readyState == 4) {
                    const akquiseArray = JSON.parse(xhr.responseText);
                    akquiseArray.forEach(akquise => {
                        akquiseList.push(new Akquise(akquise.name, akquise.id))
                    });
                    getDeals(handlePagination(xhr.getResponseHeader("Link")), currentPage + 1, akquiseList)
                }
            }
            xhr.send();
        }
    }
}, false)

I aim to execute both functions concurrently and wait until both lists are populated before transmitting the data to the service. Any suggestions or insights would be greatly appreciated!

Answer №1

I'm a bit unsure about your request for making it asynchronous. As mentioned by wOxxOm, XMLHttpRequest is an asynchronous method. Are you referring to the consolidation of results from multiple asynchronous operations? Assuming that's the case, I will address it in this response.

Understanding Asynchronous Functions

To grasp how asynchronous functions operate, let's examine a simplified version of your code. Below, there is a primary function that invokes 2 distinct asynchronous functions. Upon executing this block of code, you will see a DONE message logged in the console, followed by Async 1 complete after 1 second, and Async 2 complete one more second later.

// Copyright 2019 Google LLC.
// SPDX-License-Identifier: Apache-2.0

(function main() {
  doAsync1();
  doAsync2();
  console.log('DONE');
})()

function doAsync1() {
  setTimeout(() => {
    console.log('Async 1 complete');
  }, 1000);
}

function doAsync2() {
  setTimeout(() => {
    console.log('Async 2 complete');
  }, 2000)
}

The reason why DONE is logged before the other statements is because doAsync1 and doAsync2 are asynchronous – they take a few seconds to complete their tasks. When you call doAsync1() within main, the JavaScript engine enters the doAsync1 function and begins executing lines of code. The first line contains a setTimeout call. This function takes its initial argument and schedules it for execution 1000 milliseconds later.

After completing all tasks in doAsync1, the JavaScript engine exits that function and proceeds to the next line, which is doAsync2. Similarly, doAsync2 schedules its callback function for future execution and returns.

Subsequently, the engine executes the console.log statement, displaying DONE in the console.

After 1000 ms, the scheduled callback by doAsync1 executes and logs Async 1 complete on the console. Another 1000 ms later, the callback triggered by doAsync2 logs Async 2 complete.

Utilizing Basic Callbacks

If doAsync1 and doAsync2 generate data that we need to utilize in main once both processes are completed, we traditionally employ callbacks in JavaScript to notify us when the desired operation finishes.

// Copyright 2019 Google LLC.
// SPDX-License-Identifier: Apache-2.0

function doAsync1(callback) {
  setTimeout(() => {
    console.log('Async 1 started');
    const data = "Async 1 payload";
    callback(data);
  }, 1000);
}

function doAsync2(callback) {
  setTimeout(() => {
    console.log('Async 2 started');
    const data = "Async 2 payload";
    callback(data);
  }, 2000);
}

(function main() {
  const response = {};
  doAsync1(handleAsync1);
  doAsync2(handleAsync2);

  function handleAsync1(data) {
    response.async1 = data;
    handleComplete();
  }
  function handleAsync2(data) {
    response.async2 = data;
    handleComplete();
  }
  function handleComplete() {
    if (response.hasOwnProperty('async1') && response.hasOwnProperty('async2')) {
      console.log('DONE', response);
    }
  }
})();

Introduction to Promises

Although functional, the above approach can be quite verbose. Promises serve as an abstraction for one-time callbacks, simplifying the chaining of work blocks together.

// Copyright 2019 Google LLC.
// SPDX-License-Identifier: Apache-2.0

// Promisified version of setTimeout
function timeout(duration) {
  return new Promise(resolve => {
    setTimeout(resolve, duration);
  });
}

function doAsync1(callback) {
  return timeout(1000).then(() => {
    console.log('Async 1 started');
    const data = "Async 1 payload";
    return data;
  });
}

function doAsync2(callback) {
  return timeout(2000).then(() => {
    console.log('Async 2 started');
    const data = "Async 2 payload";
    return data;
  });
}

(function main() {
  // Initiates both doAsync1 and doAsync2 simultaneously. Upon completion of both tasks, the promise resolves with both response values.
  Promise.all([
    doAsync1(),
    doAsync2()
  ]).then(response => {
    console.log('DONE', response[0], response[1]);
  });
})();

Using Async/Await

With the advent of ES2016, we were introduced to 2 new keywords: async and await. These keywords act as syntactic sugar, enhancing the handling of promises in JavaScript. For demonstration purposes, let's convert our Promises example to async/await.

// Copyright 2019 Google LLC.
// SPDX-License-Identifier: Apache-2.0

function timeout(duration) {
  return new Promise(resolve => {
    setTimeout(resolve, duration);
  });
}

async function doAsync1(callback) {
  await timeout(1000);
  console.log('Async 1 started');
  const data = "Async 1 payload";
  return data;
}

async function doAsync2(callback) {
  await timeout(2000);
  console.log('Async 2 started');
  const data = "Async 2 payload";
  return data;
}

(async function main() {
  const response = await Promise.all([
    doAsync1(),
    doAsync2()
  ]);
  console.log('DONE', response[0], response[1]);
})();

For more in-depth information on async functions, explore Jake Archibald's article titled Async functions - making promises friendly.

Answer №2

Implement async/await functionality in your Chrome extension by using the code snippet below.

Instructions: Add the provided code snippet to both your content script and background script.

/**
 * Instructions:
 * let cookies = await asyncfy(chrome.cookies.getAll)({ url })
 * let tabs = await asyncfy(chrome.tabs.query)({active: true, currentWindow: true})
 *
 * @param fn  A function that takes one or more parameters, and the last parameter is a callback which has one or more parameter. The simplest one is chrome.management.getSelf
 * @returns {function(...[*]): Promise<any>}  Return one value if the results array has only one element, else return the whole results array
 */
let asyncfy = fn => (...args) => {
  return new Promise((resolve, reject) => {
    fn(...args, (...results) => {
      let { lastError } = chrome.runtime
      if (typeof lastError !== 'undefined') reject(lastError);
      else results.length == 1 ? resolve(results[0]) : resolve(results);
    });
  });
}


let isObject = function(obj) {
  var type = typeof obj;
  return type === 'function' || type === 'object' && !!obj;
};


// Enable async method for all functions with one callback.
let handler = {
  get: function(target, prop, receiver) {
    let value = target[prop]
    let type = typeof value
    if(type !== 'undefined') { // including null, false
      if( type === 'function') return value.bind(target); // correct the this for the functions, since we've substituted the original object to the proxy object
      return value;
    }

    if(prop.endsWith('Async')){
      let key = prop.replace(/Async$/, '')

      let method=target[key]
      let asyncMethod = asyncfy(method.bind(target));

      return asyncMethod;
    }
  }
}

// Proxy every leaf object
let asyncfyObj = handler => obj => Object.getOwnPropertyNames(obj)
  .filter(prop => isObject(obj[prop]))
  .forEach(prop => obj[prop] = new Proxy(obj[prop], handler))

// Intercept the getters of all objects in the chrome member
asyncfyObj(handler)(chrome)
asyncfyObj(handler)(chrome.storage)


// console.log(`active tab: ${JSON.stringify(await getActiveTabAsync())}`)
let getActiveTabAsync = async () => {
  let tabs = await chrome.tabs.queryAsync({active: true, currentWindow: true});
  return (tabs && tabs.length > 0) ? tabs[0] : null;
}

// chrome.storage.local.set({ foo: 'bar' });
// console.log(`foo: ${await getLocalStorageAsync('foo')}`)
let getLocalStorageAsync = async key => ( await chrome.storage.local.getAsync([key]) ) [key];

Testing: Add the following snippet to your background script and ensure that the necessary permissions are included in the manifest.json file.

(async () => {
console.log(cookies: ${JSON.stringify(await asyncfy(chrome.cookies.getAll)({ url: 'https://www.stackoverflow.com/' }))})

console.log(active tab: ${JSON.stringify(await getActiveTabAsync())})

chrome.storage.local.set({ 'foo': 'bar'});
console.log(storage: ${await getLocalStorageAsync('foo')})

console.log(extension install type: ${( await chrome.management.getSelfAsync() )['installType']})
} )()

Link to my gist

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Angular Js powered admin control panel

Explore the SB Admin Angular by Start Angular My current project involves setting up an admin dashboard on a Windows system. However, I have encountered some issues during the installation process using npm install An error message can be found here ...

programming for the final radio button text entry

I am struggling with a form that has 5 radio buttons, including an "other" option where users can manually enter text. The issue I'm facing is that the form only returns the manual entry text and not any of the preset radio values. I need it to work f ...

Is there a way to customize the animation for a button in material UI?

Trying to implement material UI in React and looking for a button that does not have the standard wave animation effect upon clicking, which you can see demonstrated here. Instead, I am searching for an animation that instantly fills the entire button wit ...

Struggling with the navbar-toggler in Bootstrap 4 Beta 2?

As part of my Bootstrap practice, I have implemented a navbar on my webpage. However, I am facing issues with the nav-bar toggler not working for small screens and the icon navbar-toggler-icon not appearing. Below is my current navbar code: <nav class ...

Can Sync blocking IO be implemented solely in JavaScript (Node.js) without the use of C code?

Is JavaScript intentionally designed to discourage or disallow synchronous blocking I/O? What is the reason for the absence of a sleep API in JavaScript? Does it relate to the previous point? Can browsers support more than one thread executing JavaScript? ...

jQuery page hanging during DOM update with large data set

I am currently using a jQuery post method with the following structure: $.post("/SearchCompetitor/Index", { username: _username }, StartLoading()) .done(function (data) { if (data !== "UsernameErro ...

Performing jQuery Ajax Request with multiple Form Elements

I have created multiple forms using my php code, as demonstrated below. My issue is that when I try to make a jquery-ajax request, I'm not sure how to reference a specific form. My objective is for the relevant form data to be posted when the submit ...

Issue when Updating Component State: React child cannot be an object

I'm currently in the process of familiarizing myself with React and how to manage component state. My goal is to build a component, set up the initial state using a constructor, make a GET request to the server to fetch an array of objects, and then ...

Issue with calling jQuery method from the view in C# not executing the function as expected

I am trying to implement jQuery in a view, but I am facing an issue where the backend method is not being triggered as expected. I need some additional eyes to help me figure out why it's not working properly. <script src="../Scripts/jquery-1.7.1. ...

Unable to retrieve post data during AJAX insertion in CodeIgniter

I am using ajax to send data to my Codeigniter controller from an external JS file. Below is the ajax code snippet I am using: $.ajax({ url: 'http://localhost/test/testController/testFunction/', ...

The Typescript SyntaxError occurs when attempting to use an import statement outside of a module, typically within a separate file that contains

I am currently developing a Minecraft bot using the mineflayer library from GitHub. To make my code more organized and reusable, I decided to switch to TypeScript and ensure readability in my project structure (see image here: https://i.stack.imgur.com/znX ...

An error of type 'TypeError' has occurred, where it is unable to access the property 'render' of an undefined element in

I'm using a function in my controller to render the "result" in my "home-page" view, which is calling my model to execute the query. exports.searchNoms = (req, res) => { getDatabaseModel.searchNoms(req).then(function(result) { console.l ...

Is there a maximum size limit for the Fabric.js Path Array?

Has anyone tried plotting a line graph using Fabric.js and encountered issues with the fabric.Path? I've noticed that it stops drawing after 8 segments even though I have attempted different methods like loops and individually coding each segment. co ...

What could be causing the .hover function to malfunction and how can I make it so that the .hover function only applies within the corner radius area?

I am attempting to make circles react to my jquery .hover function. Below is the JavaScript code I am using: jQuery.fn.center = function () { this.css("position","absolute"); this.css("top", Math.max(0, (($(window).height() - this.outerHeight()) / 2) + ...

Attempting to convert PHP tables into PDF format by utilizing jsPDF-auto-table to generate a beautifully structured PDF file containing the results of a PHP query with multiple records

I'm new to stackoverflow but I find myself visiting it regularly for helpful tips. I've taken some code from the simple.html file that comes with the jsPDF auto-table plugin. However, I'm having trouble making it work with data generated by ...

What is the best method for me to filter the values in my array?

I need to add a value to an array based on certain conditions. My code looks something like this: var newEmployee = []; $scope.employees = [{'id':1, 'new':true},{'id':2, 'new':false}{'id':3, 'new&apo ...

Unable to capture screenshot of hovered element using Cypress

Having an issue with taking a screenshot of an element with a hover effect. The screenshots always come out without the hover effect applied. tableListMaps.lineWithText('Hello world', 'myLine'); cy.get('@myLine').realH ...

Running a Blitz.js api handler triggers a Google Cloud Storage call failure with the message "error:0909006C:PEM routines:get_name:no start line"

When attempting to utilize @google-cloud/storage within a Blitz.js /api handler, I encounter the following error: error:0909006C:PEM routines:get_name:no start line at Sign.sign (internal/crypto/sig.js:110:29) at NodeCrypto.sign (C:\Users&bsol ...

The scale line on the OpenLayers map displays the same metrics twice, even when the zoom level is different

When using the Openlayers Map scale line in Metric units, a specific zoom rate may be repeated twice during the zoom event, even though the actual zoom-in resolution varies on the map. In the provided link, you can observe that the zoom rates of 5km and ...

Leverage the Power of JSON Data Manipulation in WordPress with Angular JS and the WordPress JSON

After testing code in this particular post and making some adjustments, I encountered an issue with obtaining a JSON object from the API of my blog created using the WordPress JSON plugin. URL of API from BLOG (NOT FUNCTIONING): URL from W3C example (WO ...