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

Can a function be embedded within a React render method that includes a conditional statement to update the state using setState()?

My application randomly selects three values from an array found within defaultProps and then displays these values inside div elements in the return JSX. It also assigns these values to properties in the state object. I am facing a challenge where I need ...

The session name has not been properly defined

Sorry for any errors in translation. I'm facing an issue where the session name I insert is coming up as undefined. However, when I manually enter the username, everything works fine. View Image <? php session_start (); echo var_dump ($ _SESSION ...

Implementing AngularJS JQuery Datatables Directive for Displaying Multiple Data Tables within a Single View

I have successfully implemented the following directive: angular.module('myApp').directive('jqdatatable', function () { return { restrict: 'A', link: function (scope, element, attrs, ngModelCtrl) { ...

Implementing a function to specify size limits

As a newcomer to the world of JavaScript, I am facing a particular challenge. My goal is to add a conditional statement in my code that will only execute on screen sizes larger than 768px. $(window).on('scroll', function () { if ($(window ...

Tips on deleting information from an object by utilizing Chrome storage mechanisms

In the chrome storage, I have an object structured as follows: { "planA": { 123: {key: 'some key'} 124: {key: 'some other key'} }, "planB": { 223: {key: 'some key'} 234: { ...

How can I combine various array values of equal length using a delimiter to create one final array?

I am trying to combine the values from 3 separate arrays, all of which have the same length. var title = ['title 1','title 2','title 3']; var description = ['description 1','description 2','descri ...

Modify the color scheme of the Highcharts Maps to display a range of colors according to the minimum and maximum values

I'm currently working on a Vue project where I need to display data on a world map. However, I'm facing an issue with changing the color on the map. I want to utilize the minColor and maxColor options in the colorAxis configuration, but for some ...

Issues with AngularJS Filters malfunctioning

After watching some AngularJS videos and attempting to apply a filter to a list of bookmark categories, I'm having trouble loading the main content. At the moment, I haven't implemented views in my code and would like it to remain view-less for n ...

Typescript - Error in Parsing: Expecting an expression

I am currently working with Vue and TypeScript and have encountered a problem. How can I resolve it? Here is the code snippet in question: private setTitle(systemConfig: any) { const systemConfigParse; let obj; systemConfigParse = JSON.parse(sy ...

Issue encountered while attempting to log out a user using Keycloak in a React JS application

I'm currently working on implementing authentication for a React JS app using Keycloak. To manage the global states, specifically keycloackValue and authenticated in KeycloackContext, I have opted to use React Context. Specific Cases: Upon initial r ...

Analyze the length of time and provide a percentage of similarity

Is it possible to compare two durations and calculate the percentage of similarity? Suppose I have a reference duration, as well as a second duration that needs to be compared with the first one. There is an 8% tolerance level, meaning that the second du ...

Exploring the depths of Cordova's capabilities: implementing a striking 3D front-to-back screen flip effect

Recently, I came across an interesting app that had a unique feature. By clicking on a button, the entire screen would flip from front to back with a 3D effect on iPhone. It was quite impressive to see in action. The developer mentioned that he achieved t ...

Utilizing MVC Razor and Ajax.BeginForm to dynamically update elements upon successful form submission

In my MVC search form, there is a date field that includes "-" and "+" buttons to navigate to the previous or next day, triggering a search each time. The issue I am facing is that when the -/+ button is clicked, it sends the current form data to the cont ...

How can I transmit a pong frame using WebSocket in javascript/NodeJS?

I am struggling to locate a proper example demonstrating how to send a PONG response using javascript/NodeJS within the context of a WebSocket connection (back to a server that requests it after sending a PING request). Can anyone provide guidance on thi ...

Is there a way to identify the duplicated input element values using jquery?

Just starting out in the world of web development and jQuery. I have an input element that has been bound with a blur event. Here's the code snippet: // Here are my input elements: <input class="input_name" value="bert" /> <input class="inp ...

"Eliminate a specified range of characters from a string using JavaScript

I am currently working on a JavaScript function that will remove specific characters from a string. I have tried various methods such as `string.slice()`, `string.substr()`, and `string.substring()`, but what I really need to do is remove everything before ...

Create a custom countdown using Jquery and Javascript that integrates with MySQL and PHP to display the datetime in the format Y-m-d

Hey there, I'm looking to create a countdown script using dates in the format (Y-m-d H:m:s). The goal is to retrieve the current_datetime and expire_datetime in this format and incorporate them into some JavaScript or jQuery plugin to display a countd ...

Encountered a 404 error while handling the Express 4 module, indicating that the 'html' module could not be

I need to update my express app to handle 404 (and 500) errors by displaying HTML instead of plain text. I have been able to show text to the client for 404 errors, but now I want to show HTML instead. My 404.html file is located in the /app directory. Cu ...

In my Cordova application, I am able to print the locally stored JSON array, but I am encountering an issue where the

Hello, I'm having some difficulties with JSON as a beginner. I've tried searching extensively but haven't found a solution that works for me. My issue arises when attempting to save data using local storage in JSON format - the array prints ...

What is the correct way to update the state in an array of objects using useState?

I'm struggling to figure out how to use the React useState hook properly. In my todolist, I have checkboxes and I want to update the 'done' property to 'true' for the item that has the same id as the checkbox being clicked. Even th ...