[英]How do I make my code asynchronous ? (Google Chrome Extension)
我正在开发一个 Google Chrome 扩展程序,它从两台服务器收集数据并将其发送到另一个服务。 我不明白如何使它异步。 请求似乎工作正常。
我在 Google 上搜索了一些解释,但只找到了一些有关超时的基本教程。 此外,产品服务器接受 Ajax 请求,交易服务器不接受(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)
我想调用这两个函数并等待两个列表都填满,然后将数据发送到服务。 任何想法都会帮助我!
我不太确定您所说的“使其异步”是什么意思。 正如 wOxxOm 所说,XMLHttpRequest 是一种异步方法。 你的意思是你不确定如何组合多个异步操作的结果? 为了这个答案,我假设情况就是这样。
为了分解异步函数的工作方式,让我们看一下代码的简化示例。 下面我们有一个调用 2 个不同异步函数的 main 函数。 当您运行此块时,您将收到一条DONE
消息记录到控制台, Async 1 complete
在 1 秒后Async 1 complete
记录,而Async 2 complete
在 1 秒后Async 2 complete
记录。
// 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
的原因是因为doAsync1
和doAsync2
是异步的——它们需要几秒钟才能完成它们的工作。 当您在main
调用doAsync1()
时,JS 引擎将进入doAsync1
函数并开始执行行。 第一行是setTimeout
调用。 这个函数接受它的第一个参数并安排它在 1000 毫秒后执行。
此时 JS 引擎已经在doAsync1
完成了它所能做的doAsync1
,所以它跳出该函数并调用下一行doAsync2
。 同样, doAsync2
为将来的执行和返回安排其回调。
接下来,引擎将执行console.log
行,使DONE
出现在控制台中。
1000 毫秒后,由 doAsync1 调度的回调将运行 execute 并将Async 1 complete
到控制台。 doAsync2
1000 毫秒,由doAsync2
安排的回调将记录Async 2 complete
。
现在让我们说doAsync1
和doAsync2
生成一些我们想要在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 中,我们获得了 2 个新关键字: async
和await
。 这些关键字本质上是语法糖,使在 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]);
})();
要更深入地了解异步函数,请查看Async 函数 - Jake Archibald 的承诺友好。
使用以下代码片段将 async/await 函数添加到 chrome 扩展。
用法:将以下代码片段放在内容脚本和后台脚本的开头。
/**
* 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];
测试:将以下代码段放在您的后台脚本中,并确保已将相关权限添加到 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']})
} )()
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.