【问题标题】:How do I make my code asynchronous ? (Google Chrome Extension)如何使我的代码异步? (谷歌浏览器扩展)
【发布时间】:2019-09-11 15:43:01
【问题描述】:

我正在开发一个 Google Chrome 扩展程序,它从两台服务器收集数据并将其发送到另一项服务。我不明白如何使它异步。这些请求似乎运行良好。

我在 Google 上搜索了一些解释,但只找到了一些带有超时的基本教程。此外,Product-Server 接受 Ajax 请求,而 Deal-Server 不接受(CORS 错误)。所以我使用了 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)

我想调用这两个函数并等到两个列表都填满,然后将数据发送到服务。任何想法都会帮助我!

【问题讨论】:

  • 只是一个注释.. “Product-Server 也接受 Ajax 请求,Deal-Server 不接受(CORS 错误)。所以我使用了 XMLHttpRequest。” @ 987654322@ 或 $.ajaxXMLHttpRequest. 从一个移动到另一个不一定是修复它的原因。
  • 请求已经是异步的(默认情况下),所以我猜你想要的是承诺 getProducts 和 getDeals 并执行 Promise.all() + then()。应该有很多教程。

标签: javascript ajax asynchronous google-chrome-extension promise


【解决方案1】:

我不太清楚您所说的“使其异步”是什么意思。正如 wOxxOm 所说,XMLHttpRequest 是一种异步方法。你的意思是你不确定如何组合多个异步操作的结果?为了这个答案,我假设是这样的。

基本异步

为了分解异步函数的工作原理,让我们看一个简化的代码示例。下面我们有一个调用 2 个不同异步函数的 main 函数。当你运行这个块时,你会得到一个DONE 消息记录到控制台,Async 1 complete 1 秒后记录,Async 2 complete 1 秒后记录。

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

在其他语句之前记录DONE 的原因是因为doAsync1doAsync2 是异步的——它们需要几秒钟才能完成它们的工作。当您在main 中调用doAsync1() 时,JS 引擎将进入doAsync1 函数并开始执行行。第一行是setTimeout 调用。这个函数接受它的第一个参数并安排它在 1000 毫秒后执行。

此时,JS 引擎已经完成了 doAsync1 中的所有操作,因此它退出该函数并调用下一行 doAsync2。同样,doAsync2 安排其回调以供将来执行并返回。

接下来,引擎将执行console.log 行,使DONE 出现在控制台中。

1000 毫秒后,由 doAsync1 调度的回调将运行 execute 并将 Async 1 complete 记录到控制台。再过 1000 毫秒,doAsync2 安排的回调将记录 Async 2 complete

基本回调

现在假设doAsync1doAsync2 在两者都完成后生成一些我们想要在main 中使用的数据。在 JS 中,我们传统上使用回调来在我们感兴趣的某些操作完成时获得通知。

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

承诺

虽然可以完成这项工作,但它有点冗长。 Promise 是一次性回调的抽象,可以更轻松地将工作块链接在一起。

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

异步/等待

通过ES2016,我们获得了两个新关键字:asyncawait。这些关键字本质上是语法糖,使在 JavaScript 中使用 Promise 变得更容易一些。出于演示目的,让我们看一下转换为 async/await 的 Promises 示例。

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

要更深入地了解异步函数,请查看 Jake Archibald 的 Async functions - making promises friendly

【讨论】:

  • 谢谢你,你的例子对我帮助很大。我花了一些时间,但我设法重写了我的代码,现在一切都完成了。我用过Promise.all + then()
【解决方案2】:

使用以下代码 sn-p 将 async/await 函数添加到 chrome 扩展。

用法:将以下代码 sn-p 放在两个内容脚本的开头和后台脚本。

/**
 * Usage:
 * 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;
};


// provide async method to all methods which have 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 object in 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];

测试:将以下sn-p放入你的后台脚本,并确保已将相关权限添加到manifest.json中。

(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']})
} )()

my gist

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-07-09
    • 2015-04-17
    • 2014-07-24
    • 2013-02-07
    • 2018-06-24
    相关资源
    最近更新 更多