数组数据在后台脚本的 sendResponse 中丢失
Array data lost in sendResponse from background script
我需要从内容脚本内部访问我的 API 中的数据,但是由于它们是 HTTP 请求(而不是 HTTPS),它们在内容脚本内部被阻止。
因此,我从后台脚本中执行所有请求,并尝试使用消息 API 在后台脚本和内容脚本之间进行通信。每当我准备好使用 contentscript 中的数据时,我都会向后台脚本发送一条消息,然后后台脚本将从 API 中获取数据并将其作为对 contentscript 的响应发送。从后台脚本中,如果我 console.log 在发送之前发送数据,一切都很好(4 个位置的数组)。但是contentscript中接收到的数据是一个空数组,数组中存储的所有数据都丢失了。
这是发送消息的内容脚本代码片段:
if (typeof chrome.app.isInstalled !== 'undefined')
{
console.log("gbdScreen sending requests")
chrome.runtime.sendMessage({metric: "issues"}, function(response)
{
setTimeout(function(){
if (response !== undefined)
{
console.log(response)
console.log(response.data)
}
else{
console.log("gbdScreen-else")
document.getElementById('gbdButton').click()
}
}, 2000)
})
}
这是后台脚本,它在其中接收消息、继续获取数据并将其发回:
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse)
{
let arr = []
chrome.storage.sync.get('oauth2_token', function(res)
{
if (res.oauth2_token != undefined)
{
chrome.tabs.query
({
'active': true, 'lastFocusedWindow': true
},
function (tabs)
{
let url = tabs[0].url.split("/")
let owner = url[3]
let repo = url[4].split("#")[0]
let url_aux = `?owner=${owner}&repository=${repo}&token=${res.oauth2_token}`
let url_fetch = url_base + '/commits' + url_aux
// async function to make requests
const asyncFetch = async () => await (await fetch(url_fetch))
// commits request
asyncFetch().then((resp) => resp.json()).then(function(data)
{
arr[0] = data
}).catch(function(err)
{
console.log("Error: URL = " + url_fetch + "err: " + err)
})
// issues request
url_fetch = url_base + '/issues' + url_aux
asyncFetch().then((resp) => resp.json()).then(function(data)
{
arr[1] = data
}).catch(function(err)
{
console.log("Error: URL = " + url_fetch + "err: " + err)
})
// branches request
url_fetch = url_base + '/branches' + url_aux
asyncFetch().then((resp) => resp.json()).then(function(data)
{
arr[2] = data
}).catch(function(err)
{
console.log("Error: URL = " + url_fetch + "err: " + err)
})
// prs
url_fetch = url_base + '/pullrequests' + url_aux
asyncFetch().then((resp) => resp.json()).then(function(data)
{
arr[3] = data
}).catch(function(err)
{
console.log("Error: URL = " + url_fetch + "err: " + err)
})
console.log(arr)
sendResponse({data: arr}) // sends back to screen.js the data fetched from API
})
}
})
return true
})
我 console.log 在 backgroundscript 和 contentscript 里面,它在 backgroundscript 中都很好,但在 contentscript 中打印一个空数组。如果有人能阐明一些问题,我知道现在的代码非常混乱。
这个问题基本上是 Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference 的重复。
您长时间异步填充数组 在您发送空初始 []
之后。至于 console.log,您看到最终数组只是因为 devtools 按需读取变量 - when you expand the value, not when console.log runs.
解决方案是使用 Promise.all 等待所有提取完成,然后才发送响应。
让我们使用 Mozilla's WebExtension Polyfill 来简化您的代码,这样我们就可以 return 来自 onMessage 侦听器的 Promise,而不是 return true
和 sendResponse
,或者更好地使用 async/await
:
const FETCH_TYPES = [
'commits',
'issues',
'branches',
'pullrequests',
];
async function fetchJson(type, aux) {
const url = `${url_base}/${type}${aux}`;
try {
return (await fetch(url)).json();
} catch (err) {
console.log('Error: URL =', url, 'err:', err);
}
}
browser.runtime.onMessage.addListener(async (request, sender) => {
const {oauth2_token} = await browser.storage.sync.get('oauth2_token');
if (oauth2_token) {
const url = sender.tab.url.split('/');
const owner = url[3];
const repo = url[4].split('#')[0];
const aux = `?owner=${owner}&repository=${repo}&token=${oauth2_token}`;
const data = await Promise.all(FETCH_TYPES.map(type => fetchJson(type, aux)));
return { data };
}
});
P.S。请注意代码如何使用 sender.tab.url
,因为消息可能来自非活动选项卡。
所以,wOxxOm 的回答帮助我理解了这个问题,但由于我不打算使用 Mozilla 的 WebExtension Polyfill,我通过创建一个单独的异步函数来等待 Promise 和 return获取的数据。这是因为我在消息侦听器中使用异步函数时遇到问题,即使使用 "return true",它也会在收到响应错误之前关闭消息端口。
这是后台脚本中的最终代码:
const FETCH_METRICS =
[
'commits', // 0
'issues', // 1
'branches', // 2
'pullrequests' // 3
]
async function fetchData(type, aux)
{
let url_fetch = `${url_base}/${type}/${aux}`
try
{
return (await fetch(url_fetch)).json()
}
catch (err)
{
console.log('Error: URL = ', url_fetch, ' err: ', err)
}
}
async function execute(request, aux)
{
const data_ = await Promise.all(FETCH_METRICS.map(type => fetchData(type, aux)))
return data_
}
chrome.runtime.onMessage.addListener((request, sender, sendResponse) =>
{
chrome.storage.sync.get('oauth2_token', (res) =>
{
if (res.oauth2_token != undefined)
{
chrome.tabs.query
({
'active': true, 'lastFocusedWindow': true
},
function (tabs)
{
let url = tabs[0].url.split("/")
let owner = url[3]
let repo = url[4].split("#")[0]
let url_aux = `?owner=${owner}&repository=${repo}&token=${res.oauth2_token}`
execute(request, url_aux).then(sendResponse)
})
}
})
return true
})
这是内容脚本(已经在绘制图表,响应现在已定义并填充了正确的数据):
if (typeof chrome.app.isInstalled !== 'undefined')
{
console.log("gbdScreen sending requests")
chrome.runtime.sendMessage({metric: "get-metrics"}, function(response)
{
if (response !== undefined)
{
let issuesCtx = document.getElementById('issuesDashboard').getContext('2d')
createIssuesChart(response[1], issuesCtx)
let commitCtx = document.getElementById('commitsDashboard').getContext('2d')
createCommitsChart(response[0], commitCtx)
let branchesCtx = document.getElementById('branchesDashboard').getContext('2d')
createBranchesChart(response[2], branchesCtx)
let prCtx = document.getElementById('prsDashboard').getContext('2d')
createPRChart(response[3], prCtx)
}
else{
console.log("gbdScreen-else")
document.getElementById('gbdButton').click()
}
})
}
我需要从内容脚本内部访问我的 API 中的数据,但是由于它们是 HTTP 请求(而不是 HTTPS),它们在内容脚本内部被阻止。
因此,我从后台脚本中执行所有请求,并尝试使用消息 API 在后台脚本和内容脚本之间进行通信。每当我准备好使用 contentscript 中的数据时,我都会向后台脚本发送一条消息,然后后台脚本将从 API 中获取数据并将其作为对 contentscript 的响应发送。从后台脚本中,如果我 console.log 在发送之前发送数据,一切都很好(4 个位置的数组)。但是contentscript中接收到的数据是一个空数组,数组中存储的所有数据都丢失了。
这是发送消息的内容脚本代码片段:
if (typeof chrome.app.isInstalled !== 'undefined')
{
console.log("gbdScreen sending requests")
chrome.runtime.sendMessage({metric: "issues"}, function(response)
{
setTimeout(function(){
if (response !== undefined)
{
console.log(response)
console.log(response.data)
}
else{
console.log("gbdScreen-else")
document.getElementById('gbdButton').click()
}
}, 2000)
})
}
这是后台脚本,它在其中接收消息、继续获取数据并将其发回:
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse)
{
let arr = []
chrome.storage.sync.get('oauth2_token', function(res)
{
if (res.oauth2_token != undefined)
{
chrome.tabs.query
({
'active': true, 'lastFocusedWindow': true
},
function (tabs)
{
let url = tabs[0].url.split("/")
let owner = url[3]
let repo = url[4].split("#")[0]
let url_aux = `?owner=${owner}&repository=${repo}&token=${res.oauth2_token}`
let url_fetch = url_base + '/commits' + url_aux
// async function to make requests
const asyncFetch = async () => await (await fetch(url_fetch))
// commits request
asyncFetch().then((resp) => resp.json()).then(function(data)
{
arr[0] = data
}).catch(function(err)
{
console.log("Error: URL = " + url_fetch + "err: " + err)
})
// issues request
url_fetch = url_base + '/issues' + url_aux
asyncFetch().then((resp) => resp.json()).then(function(data)
{
arr[1] = data
}).catch(function(err)
{
console.log("Error: URL = " + url_fetch + "err: " + err)
})
// branches request
url_fetch = url_base + '/branches' + url_aux
asyncFetch().then((resp) => resp.json()).then(function(data)
{
arr[2] = data
}).catch(function(err)
{
console.log("Error: URL = " + url_fetch + "err: " + err)
})
// prs
url_fetch = url_base + '/pullrequests' + url_aux
asyncFetch().then((resp) => resp.json()).then(function(data)
{
arr[3] = data
}).catch(function(err)
{
console.log("Error: URL = " + url_fetch + "err: " + err)
})
console.log(arr)
sendResponse({data: arr}) // sends back to screen.js the data fetched from API
})
}
})
return true
})
我 console.log 在 backgroundscript 和 contentscript 里面,它在 backgroundscript 中都很好,但在 contentscript 中打印一个空数组。如果有人能阐明一些问题,我知道现在的代码非常混乱。
这个问题基本上是 Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference 的重复。
您长时间异步填充数组 在您发送空初始 []
之后。至于 console.log,您看到最终数组只是因为 devtools 按需读取变量 - when you expand the value, not when console.log runs.
解决方案是使用 Promise.all 等待所有提取完成,然后才发送响应。
让我们使用 Mozilla's WebExtension Polyfill 来简化您的代码,这样我们就可以 return 来自 onMessage 侦听器的 Promise,而不是 return true
和 sendResponse
,或者更好地使用 async/await
:
const FETCH_TYPES = [
'commits',
'issues',
'branches',
'pullrequests',
];
async function fetchJson(type, aux) {
const url = `${url_base}/${type}${aux}`;
try {
return (await fetch(url)).json();
} catch (err) {
console.log('Error: URL =', url, 'err:', err);
}
}
browser.runtime.onMessage.addListener(async (request, sender) => {
const {oauth2_token} = await browser.storage.sync.get('oauth2_token');
if (oauth2_token) {
const url = sender.tab.url.split('/');
const owner = url[3];
const repo = url[4].split('#')[0];
const aux = `?owner=${owner}&repository=${repo}&token=${oauth2_token}`;
const data = await Promise.all(FETCH_TYPES.map(type => fetchJson(type, aux)));
return { data };
}
});
P.S。请注意代码如何使用 sender.tab.url
,因为消息可能来自非活动选项卡。
所以,wOxxOm 的回答帮助我理解了这个问题,但由于我不打算使用 Mozilla 的 WebExtension Polyfill,我通过创建一个单独的异步函数来等待 Promise 和 return获取的数据。这是因为我在消息侦听器中使用异步函数时遇到问题,即使使用 "return true",它也会在收到响应错误之前关闭消息端口。
这是后台脚本中的最终代码:
const FETCH_METRICS =
[
'commits', // 0
'issues', // 1
'branches', // 2
'pullrequests' // 3
]
async function fetchData(type, aux)
{
let url_fetch = `${url_base}/${type}/${aux}`
try
{
return (await fetch(url_fetch)).json()
}
catch (err)
{
console.log('Error: URL = ', url_fetch, ' err: ', err)
}
}
async function execute(request, aux)
{
const data_ = await Promise.all(FETCH_METRICS.map(type => fetchData(type, aux)))
return data_
}
chrome.runtime.onMessage.addListener((request, sender, sendResponse) =>
{
chrome.storage.sync.get('oauth2_token', (res) =>
{
if (res.oauth2_token != undefined)
{
chrome.tabs.query
({
'active': true, 'lastFocusedWindow': true
},
function (tabs)
{
let url = tabs[0].url.split("/")
let owner = url[3]
let repo = url[4].split("#")[0]
let url_aux = `?owner=${owner}&repository=${repo}&token=${res.oauth2_token}`
execute(request, url_aux).then(sendResponse)
})
}
})
return true
})
这是内容脚本(已经在绘制图表,响应现在已定义并填充了正确的数据):
if (typeof chrome.app.isInstalled !== 'undefined')
{
console.log("gbdScreen sending requests")
chrome.runtime.sendMessage({metric: "get-metrics"}, function(response)
{
if (response !== undefined)
{
let issuesCtx = document.getElementById('issuesDashboard').getContext('2d')
createIssuesChart(response[1], issuesCtx)
let commitCtx = document.getElementById('commitsDashboard').getContext('2d')
createCommitsChart(response[0], commitCtx)
let branchesCtx = document.getElementById('branchesDashboard').getContext('2d')
createBranchesChart(response[2], branchesCtx)
let prCtx = document.getElementById('prsDashboard').getContext('2d')
createPRChart(response[3], prCtx)
}
else{
console.log("gbdScreen-else")
document.getElementById('gbdButton').click()
}
})
}