javascriptajaxasynchronousgoogle-chrome-extensionpromise

How do I make my code asynchronous ? (Google Chrome Extension)


I'm developing a Google Chrome extension which collects data from two servers and sends it to another service. I don't understand how to make it asynchronous. The requests seem to work fine.

I searched Google for some explanations but just found some basic tutorials with timeouts. Also, the Product-Server accepts the Ajax request, the Deal-Server doesn't (CORS Error). So I used 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 want to call both functions and wait till both Lists are filled, then send the Data to the Service. Any idea would help me!


Solution

  • I'm not quite sure what you mean by "make it asynchronous." As wOxxOm said, XMLHttpRequest is an asynchronous method. Do you mean you're not sure how to combine the results of multiple asynchronous operations? For the sake of this answer I'll assume that's the case.

    Basic Asynchronicity

    In order to break down how asynchronous functions work, let's look at a simplified example of your code. Below we have a main function that calls 2 different asynchronous functions. When you run this block you'll get a DONE message logged to the console, Async 1 complete logged after 1 second, and Async 2 complete logged 1 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 DONE is logged before the other statements is because doAsync1 and doAsync2 are asynchronous – it take a couple seconds for them to complete their work. When you call doAsync1() in main, the JS engine will step into the doAsync1 function and start executing lines. The first line is a setTimeout call. This function takes its first argument and schedules it for execution 1000 milliseconds later.

    At this point the JS engine has done everything it can in doAsync1, so it steps out of that function and calls the next line, doAsync2. Again, doAsync2 schedules its callback for future execution and returns.

    Next, the engine will execute the console.log line which makes DONE appear in the console.

    1000 ms later, the callback scheduled by doAsync1 will run execute and log Async 1 complete to the console. Another 1000 ms later the callback scheduled by doAsync2 will log Async 2 complete.

    Basic Callbacks

    Now let's say doAsync1 and doAsync2 generate some data we want to use in main once both of complete. In JS, we traditionally use callbacks to get notified when some operation we're interested in completes.

    // 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);
        }
      }
    })();
    

    Promises

    While this does the job, it's a bit verbose. Promises are an abstraction of one-time callbacks that makes it easier to chain blocks of work 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() {
      // Starts both doAsync1 and doAsync2 at the same time. Once both complete, the
      // promise will resolve with both response values.
      Promise.all([
        doAsync1(),
        doAsync2()
      ]).then(response => {
        console.log('DONE', response[0], response[1]);
      });
    })();
    

    Async/Await

    With ES2016 we gained 2 new keywords: async and await. These keywords are essentially syntactic sugar that make it a little easier to work with promises in JavaScript. For demo purposes, let's take a look at our Promises example converted 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 doAsync1(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 a much deeper dive into async functions, check out Async functions - making promises friendly by Jake Archibald.