问题在于页面和扩展程序之间无法直接访问事件和本地对象。实际上,您获得的是一个序列化的副本,类似于执行
JSON.parse(JSON.stringify(obj))
时的结果。
这意味着某些本地对象(例如
new Error
或
new Promise
)将被清空(变成
{}
),事件将丢失,并且任何promise的实现都不能跨越边界工作。
解决方法是使用
chrome.runtime.sendMessage
来返回脚本中的消息,并在popup.js中使用
chrome.runtime.onMessage.addListener
来监听它:
chrome.tabs.executeScript(
tab.id,
{ code: `(async function() {
// Do lots of things with await
let result = true;
chrome.runtime.sendMessage(result, function (response) {
console.log(response); // Logs 'true'
});
})()` },
async emptyPromise => {
const message = new Promise(resolve => {
const listener = request => {
chrome.runtime.onMessage.removeListener(listener);
resolve(request);
};
chrome.runtime.onMessage.addListener(listener);
});
const result = await message;
console.log(result);
});
我已经将这个转换成了一个函数{{link1:chrome.tabs.executeAsyncFunction
}}(作为{{link2:chrome-extension-async
}}的一部分,该函数“promisifies”整个API):
function setupDetails(action, id) {
const wrapAsyncSendMessage = action =>
`(async function () {
const result = { asyncFuncID: '${id}' };
try {
result.content = await (${action})();
}
catch(x) {
// Make an explicit copy of the Error properties
result.error = {
message: x.message,
arguments: x.arguments,
type: x.type,
name: x.name,
stack: x.stack
};
}
finally {
// Always call sendMessage, as without it this might loop forever
chrome.runtime.sendMessage(result);
}
})()`;
let execArgs = {};
if (typeof action === 'function' || typeof action === 'string')
execArgs.code = wrapAsyncSendMessage(action);
else if (action.code) {
execArgs = action;
execArgs.code = wrapAsyncSendMessage(action.code);
}
else if (action.file)
throw new Error(`Cannot execute ${action.file}. File based execute scripts are not supported.`);
else
throw new Error(`Cannot execute ${JSON.stringify(action)}, it must be a function, string, or have a code property.`);
return execArgs;
}
function promisifyRuntimeMessage(id) {
return new Promise(resolve => {
const listener = request => {
if (request && request.asyncFuncID === id) {
chrome.runtime.onMessage.removeListener(listener);
resolve(request);
}
return false;
};
chrome.runtime.onMessage.addListener(listener);
});
}
chrome.tabs.executeAsyncFunction = async function (tab, action) {
const id = Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
const details = setupDetails(action, id);
const message = promisifyRuntimeMessage(id);
await chrome.tabs.executeScript(tab, details);
const { content, error } = await message;
if (error)
throw new Error(`Error thrown in execution script: ${error.message}.
Stack: ${error.stack}`)
return content;
}
然后可以像这样调用executeAsyncFunction
:
const result = await chrome.tabs.executeAsyncFunction(
tab.id,
async function() {
return true;
});
This 将 chrome.tabs.executeScript
和 chrome.runtime.onMessage.addListener
包装起来,并在调用 chrome.runtime.sendMessage
以解析承诺之前,在脚本中使用 try
-finally
进行包装。
chrome.tabs.executeScript
返回一个Promise,并且根据MDN文档,这也与Chrome的工作方式兼容 - 尽管我还没有找到任何有用的Chrome文档,但这可能是需要考虑的事情。 - Jaromanda Xchrome.tabs.executeScript
已经返回了一个 Promise - 所以,你已经拥有了你所期望的 Promise。 - Jaromanda X