javascript - 使用 chrome.tabs.executeScript 执行异步函数

标签 javascript google-chrome-extension async-await es6-promise

我有一个功能,我想使用 chrome.tabs.executeScript 在页面中执行,从浏览器操作弹出窗口运行。权限设置正确,并且可以正常使用同步回调:

chrome.tabs.executeScript(
    tab.id, 
    { code: `(function() { 
        // Do lots of things
        return true; 
    })()` },
    r => console.log(r[0])); // Logs true

问题是我要调用的函数要经过几个回调,所以我想使用asyncawait:

chrome.tabs.executeScript(
    tab.id, 
    { code: `(async function() { 
        // Do lots of things with await
        return true; 
    })()` },
    async r => {
        console.log(r); // Logs array with single value [Object]
        console.log(await r[0]); // Logs empty Object {}
    }); 

问题是回调结果r。它应该是一组脚本结果,所以我希望 r[0] 是一个在脚本完成时解析的 promise 。

Promise 语法(使用 .then())也不起作用。

如果我在页面中执行完全相同的函数,它会按预期返回一个 promise 并且可以等待。

知道我做错了什么吗?有什么解决办法吗?

最佳答案

问题是事件和 native 对象不能直接在页面和扩展之间使用。本质上,您会得到一个序列化副本,就像您执行 JSON.parse(JSON.stringify(obj)) 时一样。

这意味着一些原生对象(例如 new Errornew 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 => {

        // Create a promise that resolves when chrome.runtime.onMessage fires
        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); // Logs true
    }); 

我已经 extended this into a function chrome.tabs.executeAsyncFunction (作为 chrome-extension-async 的一部分,它“ promise ”了整个 API):

function setupDetails(action, id) {
    // Wrap the async function in an await and a runtime.sendMessage with the result
    // This should always call runtime.sendMessage, even if an error is thrown
    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);
    }
})()`;

    // Apply this wrapper to the code passed
    let execArgs = {};
    if (typeof action === 'function' || typeof action === 'string')
        // Passed a function or string, wrap it directly
        execArgs.code = wrapAsyncSendMessage(action);
    else if (action.code) {
        // Passed details object https://developer.chrome.com/extensions/tabs#method-executeScript
        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) {
    // We don't have a reject because the finally in the script wrapper should ensure this always gets called.
    return new Promise(resolve => {
        const listener = request => {
            // Check that the message sent is intended for this listener
            if (request && request.asyncFuncID === id) {

                // Remove this listener
                chrome.runtime.onMessage.removeListener(listener);
                resolve(request);
            }

            // Return false as we don't want to keep this channel open https://developer.chrome.com/extensions/runtime#event-onMessage
            return false;
        };

        chrome.runtime.onMessage.addListener(listener);
    });
}

chrome.tabs.executeAsyncFunction = async function (tab, action) {

    // Generate a random 4-char key to avoid clashes if called multiple times
    const id = Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);

    const details = setupDetails(action, id);
    const message = promisifyRuntimeMessage(id);

    // This will return a serialised promise, which will be broken
    await chrome.tabs.executeScript(tab, details);

    // Wait until we have the result message
    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 to execute in the page
    async function() { 
        // Do lots of things with await
        return true; 
    });

This包装 chrome.tabs.executeScriptchrome.runtime.onMessage.addListener,并将脚本包装在 try-finally 在调用 chrome.runtime.sendMessage 以解决 promise 之前。

关于javascript - 使用 chrome.tabs.executeScript 执行异步函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43144485/

相关文章:

javascript - 将空字符串返回到 ES6 模板字符串中

javascript - 遍历文件夹

javascript - Access-Control-Allow-Origin 不 checkin chrome 扩展

javascript - 如何获取多个表中每个元素的innerText?(chrome扩展)

javascript - 语法错误 : Unexpected token function - Async Await Nodejs

c# - Asp.Net MVC、WebApi 和正确的异步方法

javascript - 将变量设置为 $scope 值

javascript - Highcharts:是否可以显示类似于饼图图例的旭日图例?

javascript - 什么是 Service Worker 的 URL.createObjectURL 的模拟

javascript - return 不等待异步函数