返回没有功能的 Chrome 存储 API 值

IT技术 javascript function google-chrome-extension return-value google-chrome-storage
2021-02-07 20:18:19

在过去的两天里,我一直在使用 chrome 异步存储。如果你有一个功能,它就可以“正常”工作。(如下图):

chrome.storage.sync.get({"disableautoplay": true}, function(e){
console.log(e.disableautoplay);
});

我的问题是我不能在我正在做的事情中使用一个函数。我只想返回它,就像 LocalStorage 一样。就像是:

var a = chrome.storage.sync.get({"disableautoplay": true});

或者

var a = chrome.storage.sync.get({"disableautoplay": true}, function(e){
    return e.disableautoplay;
});

我已经尝试了一百万种组合,甚至设置了一个公共变量并设置:

var a;
window.onload = function(){
    chrome.storage.sync.get({"disableautoplay": true}, function(e){
    a = e.disableautoplay;
});
}

没有任何作用。除非引用它的代码在 get 的函数内,否则它都返回 undefined,这对我来说没用。我只想能够将值作为变量返回。

这甚至可能吗?

编辑:这个问题不是重复的,请允许我解释原因:

1:没有其他帖子专门问这个(我花了两天时间先看看,以防万一)。

2:我的问题还没有得到解答。是的,Chrome 存储是异步的,是的,它不返回值。那就是问题所在。下面我详细说...

我需要能够在 chrome.storage.sync.get 函数之外获取存储值。我 - 不能 - 使用 localStorage,因为它是特定于 url 的,并且无法从 chrome 扩展的 browser_action 页面和 background.js 访问相同的值。我无法使用一个脚本存储值并使用另一个脚本访问它。他们是分开对待的。

所以我唯一的解决方案是使用 Chrome Storage。必须有某种方法来获取存储项的值并在 get 函数之外引用它。我需要在 if 语句中检查它。

就像 localStorage 可以做的一样

if(localStorage.getItem("disableautoplay") == true);

必须有某种方式来做一些事情

if(chrome.storage.sync.get("disableautoplay") == true);

我意识到它不会那么简单,但这是我能解释它的最好方式。

我看到的每个帖子都说要这样做:

chrome.storage.sync.get({"disableautoplay": true, function(i){
    console.log(i.disableautoplay);
    //But the info is worthless to me inside this function.
});
//I need it outside this function.
5个回答

这是针对您的问题的量身定制的答案。为什么你不能绕过异步,它仍然会是 90% 长的解释,但请耐心等待 - 它通常会对你有所帮助。我保证最后会有一些相关的东西chrome.storage

在我们开始之前,我将重申规范链接:

那么,让我们来讨论一下 JS 异步性。

第 1 部分:它是什么?

要涵盖的第一个概念是运行时环境在某种程度上,JavaScript 被嵌入到另一个控制其执行流程的程序中——在本例中是 Chrome。发生的所有事件(计时器、点击等)都来自运行时环境。JavaScript 代码为事件注册处理程序,这些处理程序由运行时记住并在适当时调用。

其次,理解 JavaScript 是单线程的很重要。运行时环境维护一个单独的事件循环如果在某个事件发生时还有其他一些代码在执行,则该事件会被放入队列以在当前代码终止时进行处理

看看这段代码:

var clicks = 0;

someCode();
element.addEventListener("click", function(e) {
  console.log("Oh hey, I'm clicked!");
  clicks += 1;
});
someMoreCode();

那么,这里发生了什么?当这段代码执行时,当执行到达 时.addEventListener,会发生以下情况:运行时环境被通知当事件发生(element被点击)时,它应该调用处理程序函数。

重要的是要了解(尽管在这种特殊情况下很明显)该函数此时运行。它只会稍后运行,当该事件发生时。一旦运行时确认“我将运行(或“回调”,因此名称为“回调”)发生这种情况时,执行就会继续。如果someMoreCode()尝试访问clicks,它将是0,而不是1

这就是所谓的异步性,因为这是在当前执行流程之外发生的事情。

第 2 部分:为什么需要它,或者为什么同步 API 正在消失?

现在,一个重要的考虑因素。假设这someMoreCode()实际上是一段运行时间很长的代码。如果在它仍在运行时发生点击事件会发生什么?

JavaScript 没有中断的概念。运行时会看到有代码在执行,并将事件处理程序调用放入队列中。处理程序在someMoreCode()完全完成之前不会执行

虽然单击事件处理程序在不能保证单击发生的意义上是极端的,但这解释了为什么您不能等待异步操作的结果这是一个不起作用的例子:

element.addEventListener("click", function(e) {
  console.log("Oh hey, I'm clicked!");
  clicks += 1;
});
while(1) {
  if(clicks > 0) {
    console.log("Oh, hey, we clicked indeed!");
    break;
  }
}

你可以点击你的心的内容,但会增加的代码clicks耐心地等待(非终止)循环终止。oop。

请注意,这段代码不仅冻结了这段代码:在我们等待时不再处理每个事件,因为只有一个事件队列/线程。JavaScript 中只有一种方法可以让其他处理程序完成它们的工作:终止当前代码,并让运行时知道在发生我们想要的事情时调用什么


这就是为什么将异步处理应用于另一类调用的原因:

  • 需要运行时而不是 JS 来做某事(例如磁盘/网络访问)
  • 保证终止(无论成功或失败)

让我们来看一个经典的例子:AJAX 调用。假设我们要从 URL 加载文件。

  • 假设在我们当前的连接上,运行时可以在 100 毫秒内以 JS 中可以使用的形式请求、下载和处理文件。
  • 在另一个连接上,这有点糟糕,需要 500 毫秒。
  • 有时连接真的很糟糕,所以运行时会等待 1000 毫秒并超时放弃。

如果我们要等到这完成,我们就会有一个可变的、不可预测的和相对较长的延迟。由于 JS 等待的工作方式,所有其他处理程序(例如 UI)不会在此延迟期间完成其工作,从而导致页面冻结。

听起来很熟悉?是的,这正是同步 XMLHttpRequest 的工作方式它不是while(1)JS 代码中的循环,而是本质上发生在运行时代码中——因为 JavaScript 在等待时不能让其他代码执行。

是的,这允许使用熟悉的代码形式:

var file = get("http://example.com/cat_video.mp4");

但是一切都冻结了,代价是可怕的、可怕的。成本如此可怕,事实上,现代浏览器认为这已被弃用。这是关于 MDN 主题讨论


现在让我们看看localStorage它符合“终止对运行时的调用”的描述,但它是同步的。为什么?

简单地说:历史原因(这是一个非常古老的规范)。

虽然它肯定比网络请求更可预测,localStorage但仍然需要以下链:

JS code <-> Runtime <-> Storage DB <-> Cache <-> File storage on disk

这是一个复杂的事件链,需要暂停整个 JS 引擎。这导致了被认为是不可接受的性能


现在,Chrome API 从头开始​​就是为性能而设计的。您仍然可以在较旧的API 中看到一些同步调用,例如chrome.extension,并且有些调用是在 JS 中处理的(因此作为同步有意义)但chrome.storage(相对)是新的。

因此,如果在运行时执行某些操作时会出现延迟,则它采用范式“我确认您的调用并将返回结果,现在同时做一些有用的事情”与 XMLHttpRequest 不同,这些调用没有同步版本。

引用文档:

它 [ chrome.storage] 与批量读取和写入操作异步,因此比阻塞和串行 localStorageAPI更快

第 3 部分:如何接受异步性?

处理异步性的经典方法是回调链。

假设您有以下同步代码:

var result = doSomething();
doSomethingElse(result);

假设现在doSomething是异步的。然后就变成了:

doSomething(function(result) {
  doSomethingElse(result);
});

但如果它更复杂呢?说是:

function doABunchOfThings() {
  var intermediate = doSomething();
  return doSomethingElse(intermediate);
}

if (doABunchOfThings() == 42) {
  andNowForSomethingCompletelyDifferent()
}

嗯.. 在这种情况下,您需要在回调中移动所有这些。return必须改为调用。

function doABunchOfThings(callback) {
  doSomething(function(intermediate) {
    callback(doSomethingElse(intermediate));
  });
}

doABunchOfThings(function(result) {
  if (result == 42) {
    andNowForSomethingCompletelyDifferent();
  }
});

这里有一系列回调:立即doABunchOfThings调用doSomething,终止,但稍后调用doSomethingElse,其结果if通过另一个回调提供。

显然,这种分层会变得混乱。好吧,没有人说 JavaScript 是一门好语言.. 欢迎来到回调地狱

有一些工具可以使其更易于管理,例如Promisesasync/await我不会在这里讨论它们(空间不足),但它们不会改变基本的“此代码只会稍后运行”部分。

TL;DR 部分:我绝对必须有存储同步,呸!

有时有正当理由需要同步存储。例如,webRequestAPI 阻塞调用不能等待。或者回调地狱会让你付出惨痛的代价。

你可以做的是有一个同步缓存异步的chrome.storage它会带来一些成本,但这并非不可能。

考虑:

var storageCache = {};
chrome.storage.sync.get(null, function(data) {
  storageCache = data;
  // Now you have a synchronous snapshot!
});
// Not HERE, though, not until "inner" code runs

如果您可以将所有初始化代码放在一个函数中init(),那么您将拥有:

var storageCache = {};
chrome.storage.sync.get(null, function(data) {
  storageCache = data;
  init(); // All your code is contained here, or executes later that this
});

到代码init()执行时,然后当任何分配给处理程序的事件init()发生时storageCache将被填充。您已将异步性降低到 ONE 回调。

当然,这只是在执行时存储看起来的快照get()如果你想保持与存储的一致性,你需要设置更新到storageCachevia chrome.storage.onChangedevents由于 JS 的单事件循环特性,这意味着缓存只会在您的代码不运行时更新,但在许多情况下这是可以接受的。

同样,如果要将更改传播到storageCache实际存储,仅设置storageCache['key']是不够的。您需要编写一个set(key, value)shim,既写入storageCache又调度 (asynchronous) chrome.storage.sync.set

实现这些留作练习。

@rambossa 这是一个cliffsnotes 版本:“API 本身是异步的,所以你不能用它做真正同步的事情——请学习如何处理异步 JS。” 剩下的就是关于异步 JS 的“什么和为什么”。
2021-03-11 20:18:19
这个答案是高不可攀的。
2021-03-15 20:18:19
虽然异步情况可以工作,但几乎不可能处理存储 api 可以返回的错误情况,特别是同步 api 中的速率限制并不少见。
2021-03-23 20:18:19
精彩的答案,即使答案是否定的。我发现,仅将init()代码放入回调中是不够的。我会将整个内容脚本放在那里。基本上,加载缓存然后开始工作。然后,正如你所提到的,保持更新onChange事件。然后,只要您始终使用 ,storageCache您将获得即时访问读/写。然后通常“同步”方面变得几乎无关紧要。它发生,无论何时,谁在乎。因此,除非您的用户在您的内容脚本更改存储的同时单击 PageAction 按钮……否则应该没问题。
2021-04-02 20:18:19

使主函数“异步”并在其中创建“Promise”:)

async function mainFuction() {
    var p = new Promise(function(resolve, reject){
        chrome.storage.sync.get({"disableautoplay": true}, function(options){
            resolve(options.disableautoplay);
        })
    });

    const configOut = await p;
    console.log(configOut);
}
这太棒了,一直在寻找这个。做得漂亮。
2021-03-25 20:18:19
感谢这个非常简单的答案,它直接解决了 OP 的要求。在调用层次结构中 async/await 的一些应用程序导致 mainFunction() 和我都准备好了!
2021-03-26 20:18:19

是的,您可以使用 promise 来实现:

let getFromStorage = keys => new Promise((resolve, reject) =>
  chrome.storage.sync.get(...keys, result => resolve(result)));
是的,这是一半,但它仍然使调用异步。你只能await在一个async函数中使用
2021-03-31 20:18:19
  1. chrome.storage.sync.get没有返回值,这解释了为什么undefined在调用类似的东西时会得到

    var a = chrome.storage.sync.get({"disableautoplay": true});
    
  2. chrome.storage.sync.get也是一个异步方法,这就解释了为什么在下面的代码aundefined除非你在回调函数中访问它。

    var a;
    window.onload = function(){
        chrome.storage.sync.get({"disableautoplay": true}, function(e){
            // #2
            a = e.disableautoplay; // true or false
        });
        // #1
        a; // undefined
    }
    
@Xan,明白了,我会开始做笔记来记录流行的扩展相关问题。
2021-03-11 20:18:19
@KyleHickey,为什么不将“从它返回的值中获取值”逻辑放在回调中?我的建议仍然是“了解回调并使用它”,因为chrome.* apis 中的大多数方法都是异步的
2021-03-20 20:18:19
我意识到它没有返回值,但我需要以某种方式从它返回的值中获取值......必须有某种方法来获取它。
2021-03-23 20:18:19
Haibara,请花点时间为欺骗目标和我在评论中链接的额外问题添加书签。这些是经常需要的参考。
2021-04-09 20:18:19

如果你能设法解决这个问题,你就会成为奇怪错误的来源。消息是异步执行的,这意味着当您发送消息时,其余代码可以在异步函数返回之前执行。由于 chrome 是多线程的并且 get 函数可能会延迟,即 hdd 很忙,因此无法保证这一点。以您的代码为例:

var a;
window.onload = function(){
    chrome.storage.sync.get({"disableautoplay": true}, function(e){
    a = e.disableautoplay;
});
}

if(a)
   console.log("true!");
else
   console.log("false! Maybe undefined as well. Strange if you know that a is true, right?");

所以如果你使用这样的东西会更好:

chrome.storage.sync.get({"disableautoplay": true}, function(e){
    a = e.disableautoplay;
    if(a)
      console.log("true!");
    else
      console.log("false! But maybe undefined as well");
});

如果您真的想返回此值,请使用 javascript 存储 API。这仅存储字符串值,因此您必须在存储之前和获取之后转换该值。

//Setting the value
localStorage.setItem('disableautoplay', JSON.stringify(true));

//Getting the value
var a = JSON.stringify(localStorage.getItem('disableautoplay'));
谢谢你,泽。我正准备使用 LocalStorage,但如果可能的话,我真的很想使用 Chrome 同步存储。我想我必须
2021-04-06 20:18:19