Возврат значения API хранилища Chrome без функции

Последние два дня я работаю с асинхронным хранилищем 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, и это бесполезно для меня. Я просто хочу иметь возможность возвращать значение в качестве переменной.

Это вообще возможно?

РЕДАКТИРОВАТЬ: Этот вопрос не является дубликатом, пожалуйста, позвольте мне объяснить, почему:

1: нет других постов, спрашивающих об этом конкретно (я потратил два дня на поиски, на всякий случай).

2: мой вопрос все еще не отвечен. Да, Chrome Storage является асинхронным, и да, он не возвращает значение. Это проблема. Я уточню ниже...

Мне нужно иметь возможность получить сохраненное значение вне функции chrome.storage.sync.get. Я не могу использовать localStorage, так как он специфичен для URL, и к тем же значениям нельзя получить доступ как со страницы browser_action расширения chrome, так и из 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.

6 ответов

Хорошо, хорошо, вы получите свой индивидуальный ответ на свой вопрос. Это все еще будет 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 терпеливо ждет окончания (не завершающегося) цикла. К сожалению.

Обратите внимание, что этот фрагмент кода не только замораживает этот фрагмент кода: каждое отдельное событие больше не обрабатывается, пока мы ожидаем, потому что существует только одна очередь / поток событий. В JavaScript есть только один способ позволить другим обработчикам выполнять свою работу: завершать текущий код и сообщать среде выполнения, что вызывать, когда происходит что-то, что нам нужно.


Вот почему асинхронная обработка применяется к другому классу вызовов, которые:

  • требует выполнения, а не JS, чтобы что-то делать (например, доступ к диску / сети)
  • гарантированно прекратить (в случае успеха или неудачи)

Давайте рассмотрим классический пример: вызовы AJAX. Предположим, мы хотим загрузить файл с URL.

  • Допустим, что при нашем текущем соединении среда выполнения может запрашивать, загружать и обрабатывать файл в форме, которую можно использовать в JS за 100 мс.
  • С другой стороны, это немного хуже, это займет 500 мс.
  • И иногда соединение действительно плохое, поэтому время выполнения будет ждать 1000 мс и сдается с таймаутом.

Если бы мы подождали, пока это не завершится, у нас была бы переменная, непредсказуемая и относительно длительная задержка. Из-за того, как работает ожидание JS, все другие обработчики (например, пользовательский интерфейс) не будут выполнять свою работу в течение этой задержки, что приведет к зависанию страницы.

Звучит знакомо? Да, именно так работает синхронный 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 должен быть остановлен для этого. Это приводит к тому, что считается неприемлемым исполнением.


Теперь API-интерфейсы Chrome изначально созданы для повышения производительности. Вы все еще можете увидеть некоторые синхронные вызовы в старых API, таких как chrome.extension и есть вызовы, которые обрабатываются в JS (и поэтому имеют смысл как синхронные), но chrome.storage является (относительно) новым.

Как таковая, она включает в себя парадигму "Я подтверждаю ваш звонок и вернусь с результатами, а пока что сделаю что-то полезное", если есть задержка, связанная с выполнением чего-либо во время выполнения. Синхронных версий этих вызовов нет, в отличие от XMLHttpRequest.

Цитирование документов:

Это [ chrome.storage ] асинхронный с массовыми операциями чтения и записи, и, следовательно, быстрее, чем блокировка и последовательный localStorage API.

Раздел 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 - хороший язык. Добро пожаловать в Callback Hell.

Есть инструменты, чтобы сделать его более управляемым, например, обещания. Я не буду обсуждать их здесь (не хватает места), но они не меняют основную часть "этот код будет выполняться только позже".

Секция TL;DR: мне абсолютно необходимо, чтобы хранилище было синхронным, хелп!

Иногда есть законные причины иметь синхронное хранилище. Например, webRequest API блокировка звонков не может ждать. Или Callback Hell будет стоить вам дорого.

Что вы можете сделать, это иметь синхронный кэш асинхронного 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 будет заселен. Вы уменьшили асинхронность до ОДНОГО обратного вызова.

Конечно, это только снимок того, что хранилище смотрит во время выполнения get(), Если вы хотите сохранить согласованность с хранилищем, вам нужно настроить обновления на storageCache с помощью chrome.storage.onChanged события Из-за JS-цикла с единичными событиями это означает, что кэш будет обновляться только тогда, когда ваш код не выполняется, но во многих случаях это приемлемо.

Точно так же, если вы хотите распространять изменения в storageCache в реальное хранилище, просто установив storageCache['key'] недостаточно. Вам нужно будет написать set(key, value) шим что ОБА пишет storageCache и планирует (асинхронный) chrome.storage.sync.set,

Реализация их остается в качестве упражнения.

Сделайте основную функцию async и сделайте в ней обещание:)

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);
}

Да, вы можете добиться этого с помощью обещания:

let getFromStorage = keys => new Promise((resolve, reject) =>
  chrome.storage.sync.get(...keys, result => resolve(result)));
  1. chrome.storage.sync.get не имеет возвращаемых значений, что объясняет, почему вы получите undefined при звонке что-то вроде

    var a = chrome.storage.sync.get({"disableautoplay": true});
    
  2. chrome.storage.sync.get также асинхронный метод, который объясняет, почему в следующем коде a было бы undefined если вы не получите доступ к нему внутри функции обратного вызова.

    var a;
    window.onload = function(){
        chrome.storage.sync.get({"disableautoplay": true}, function(e){
            // #2
            a = e.disableautoplay; // true or false
        });
        // #1
        a; // undefined
    }
    

Если вам удастся решить эту проблему, вы получите источник странных ошибок. Сообщения выполняются асинхронно, что означает, что когда вы отправляете сообщение, остальная часть кода может выполняться до того, как вернется асинхронная функция. Это не гарантируется, поскольку 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");
});

Если вы действительно хотите вернуть это значение, используйте API хранилища javascript. Здесь хранятся только строковые значения, поэтому вам нужно привести значение до сохранения и после его получения.

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

//Getting the value
var a = JSON.stringify(localStorage.getItem('disableautoplay'));
      var a = await chrome.storage.sync.get({"disableautoplay": true});

Это должно быть в асинхронной функции. например, если вам нужно запустить его на верхнем уровне, оберните его:

      (async () => {
  var a = await chrome.storage.sync.get({"disableautoplay": true});
})();
Другие вопросы по тегам