javascript - Devolver el valor de la API de almacenamiento de Chrome sin función
function google-chrome-extension (3)
Durante los últimos dos días he estado trabajando con el almacenamiento asincrónico de Chrome. Funciona "bien" si tiene una función. (Como abajo):
chrome.storage.sync.get({"disableautoplay": true}, function(e){
console.log(e.disableautoplay);
});
Mi problema es que no puedo usar una función con lo que estoy haciendo. Solo quiero devolverlo, como puede hacerlo LocalStorage. Algo como:
var a = chrome.storage.sync.get({"disableautoplay": true});
o
var a = chrome.storage.sync.get({"disableautoplay": true}, function(e){
return e.disableautoplay;
});
He intentado un millón de combinaciones, incluso estableciendo una variable pública y estableciendo que:
var a;
window.onload = function(){
chrome.storage.sync.get({"disableautoplay": true}, function(e){
a = e.disableautoplay;
});
}
Nada funciona. Todo vuelve indefinido a menos que el código que hace referencia esté dentro de la función de get, y eso es inútil para mí. Solo quiero poder devolver un valor como variable.
¿Es esto posible?
EDITAR: Esta pregunta no es un duplicado, permítame explicar por qué:
1: No hay otras publicaciones que pregunten esto específicamente (pasé dos días buscando primero, por si acaso).
2: Mi pregunta aún no ha sido respondida. Sí, Chrome Storage es asíncrono, y sí, no devuelve un valor. Ese es el problema. Voy a elaborar a continuación ...
Necesito poder obtener un valor almacenado fuera de la función chrome.storage.sync.get. No puedo usar localStorage, ya que es específico de la URL, y no se puede acceder a los mismos valores desde la página browser_action de la extensión de Chrome y desde background.js. No puedo almacenar un valor con un script y acceder a él con otro. Son tratados por separado.
Entonces, mi única solución es usar Chrome Storage. Debe haber alguna forma de obtener el valor de un elemento almacenado y hacer referencia a él fuera de la función get. Necesito verificarlo en una declaración if.
Justo como lo puede hacer localStorage
if(localStorage.getItem("disableautoplay") == true);
Tiene que haber alguna forma de hacer algo en la línea de
if(chrome.storage.sync.get("disableautoplay") == true);
Me doy cuenta de que no será así de simple, pero esa es la mejor manera en que puedo explicarlo.
Cada publicación que veo dice hacerlo de esta manera:
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.
-
chrome.storage.sync.get
no tiene valores devueltos, lo que explica por qué no seundefined
al llamar a algo comovar a = chrome.storage.sync.get({"disableautoplay": true});
-
chrome.storage.sync.get
también es un método asincrónico , que explica por qué en el siguiente códigoa
no estaríaundefined
menos que acceda a él dentro de la función de devolución de llamada.var a; window.onload = function(){ chrome.storage.sync.get({"disableautoplay": true}, function(e){ // #2 a = e.disableautoplay; // true or false }); // #1 a; // undefined }
De acuerdo, está bien, obtienes tu respuesta personalizada a tu pregunta.
Todavía será una explicación del 90% de por qué no puede moverse de forma asíncrona, pero tenga paciencia conmigo, lo ayudará en general.
Prometo que hay algo pertinente para
chrome.storage
al final.
Antes de comenzar, reiteraré los enlaces canónicos para esto:
-
.com/questions/11688171/…
(Específico de Chrome, excelente respuesta de RobW, probablemente más fácil de entender ) - ¿Por qué no se modifica mi variable después de modificarla dentro de una función? - Referencia de código asíncrono ( referencia canónica general sobre lo que está pidiendo)
-
¿Cómo devuelvo la respuesta de una llamada asincrónica?
(una pregunta canónica más antigua pero no menos respetada sobre JS asíncrono) - You Don''t Know JS: Async & Performance (ebook on JS asynchronicity)
Entonces, discutamos la asincronía de JS.
Sección 1: ¿Qué es?
El primer concepto a cubrir es el entorno de tiempo de ejecución . JavaScript está, de alguna manera, incrustado en otro programa que controla su flujo de ejecución, en este caso, Chrome. Todos los eventos que suceden (temporizadores, clics, etc.) provienen del entorno de tiempo de ejecución. El código JavaScript registra controladores para eventos, que son recordados por el tiempo de ejecución y se llaman según corresponda.
En segundo lugar, es importante comprender que JavaScript es de un solo subproceso. Hay un único bucle de eventos mantenido por el entorno de tiempo de ejecución; Si hay algún otro código ejecutándose cuando ocurre un evento, ese evento se pone en una cola para ser procesado cuando el código actual termina .
Echa un vistazo a este código:
var clicks = 0;
someCode();
element.addEventListener("click", function(e) {
console.log("Oh hey, I''m clicked!");
clicks += 1;
});
someMoreCode();
¿Entonces, Que esta pasando aquí?
A medida que se ejecuta este código, cuando la ejecución alcanza
.addEventListener
, sucede lo siguiente:
se notifica al entorno de ejecución que cuando ocurre el evento (se hace clic en el
element
), debe llamar a la función de controlador.
Es importante entender (aunque en este caso particular es bastante obvio) que la función
no se
ejecuta en este momento.
Solo se ejecutará
más tarde
, cuando ocurra ese evento.
La ejecución continúa tan pronto como el tiempo de ejecución reconoce
"Ejecutaré (o" devolver la llamada ", de ahí el nombre" devolución de llamada ") esto cuando eso suceda".
Si
someMoreCode()
intenta acceder a los
clicks
, será
0
, no
1
.
Esto es lo que se llama asincronía , ya que esto es algo que sucederá fuera del flujo de ejecución actual.
Sección 2: ¿Por qué es necesario o por qué las API síncronas están desapareciendo?
Ahora, una consideración importante.
Supongamos que
someMoreCode()
es en realidad un fragmento de código de muy larga duración.
¿Qué sucederá si se produce un evento de clic mientras aún se está ejecutando?
JavaScript no tiene concepto de interrupciones.
El tiempo de ejecución verá que hay código ejecutándose y pondrá la llamada del controlador de eventos en la cola.
El controlador no se ejecutará antes de que
someMoreCode()
finalice por completo.
Si bien un controlador de eventos de clic es extremo en el sentido de que no se garantiza que el clic ocurra, esto explica por qué no puede esperar el resultado de una operación asincrónica . Aquí hay un ejemplo que no funcionará:
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;
}
}
Puede hacer clic en el contenido de su corazón, pero el código que incrementaría los
clicks
está esperando pacientemente a que finalice el bucle (sin finalización).
Ups
Tenga en cuenta que este código no solo congela este código: cada evento ya no se maneja mientras esperamos , porque solo hay una cola / subproceso de eventos. Solo hay una forma en JavaScript para permitir que otros manejadores hagan su trabajo: terminar el código actual y dejar que el tiempo de ejecución sepa a qué llamar cuando ocurre algo que queremos .
Es por eso que el tratamiento asincrónico se aplica a otra clase de llamadas que:
- requiere el tiempo de ejecución, y no JS, para hacer algo (acceso a disco / red, por ejemplo)
- están garantizados para terminar (ya sea en caso de éxito o fracaso)
Vayamos con un ejemplo clásico: llamadas AJAX. Supongamos que queremos cargar un archivo desde una URL.
- Digamos que en nuestra conexión actual, el tiempo de ejecución puede solicitar, descargar y procesar el archivo en la forma que se puede utilizar en JS en 100 ms.
- En otra conexión, eso es un poco peor, tomaría 500 ms.
- Y a veces la conexión es realmente mala, por lo que el tiempo de ejecución esperará 1000 ms y se dará por vencido con un tiempo de espera.
Si tuviéramos que esperar hasta que esto se complete, tendríamos un retraso variable, impredecible y relativamente largo. Debido a cómo funciona la espera JS, todos los demás manejadores (por ejemplo, UI) no harían su trabajo por este retraso, lo que llevaría a una página congelada.
¿Suena familiar?
Sí, así es exactamente cómo
funciona XMLHttpRequest síncrono
.
En lugar de un ciclo
while(1)
en el código JS, esencialmente sucede en el código de tiempo de ejecución, ya que JavaScript no puede permitir que otro código se ejecute mientras está esperando.
Sí, esto permite una forma familiar de código:
var file = get("http://example.com/cat_video.mp4");
Pero a un costo terrible, terrible de que todo se congele. Un costo tan terrible que, de hecho, los navegadores modernos consideran que esto está en desuso. Aquí hay una discusión sobre el tema en MDN .
Ahora echemos un vistazo a
localStorage
.
Coincide con la descripción de "finalizar la llamada al tiempo de ejecución" y, sin embargo, es sincrónico.
¿Por qué?
En pocas palabras: razones históricas (es una especificación muy antigua).
Si bien es más predecible que una solicitud de red,
localStorage
aún necesita la siguiente cadena:
JS code <-> Runtime <-> Storage DB <-> Cache <-> File storage on disk
Es una cadena compleja de eventos, y todo el motor JS debe pausarse para ello. Esto lleva a lo que se considera un rendimiento inaceptable .
Ahora, las API de Chrome están, desde cero, diseñadas para el rendimiento.
Todavía puede ver algunas llamadas síncronas en API
anteriores
como
chrome.extension
, y hay llamadas que se manejan en JS (y, por lo tanto, tienen sentido como síncronas), pero
chrome.storage
es (relativamente) nuevo.
Como tal, abarca el paradigma "Reconozco su llamada y volveré con los resultados, ahora haga algo útil mientras tanto" si hay un retraso involucrado en hacer algo con el tiempo de ejecución. No hay versiones sincrónicas de esas llamadas, a diferencia de XMLHttpRequest.
Citando los documentos:
Es [
chrome.storage
] asíncrono con operaciones de lectura y escritura masivas, y por lo tanto más rápido que el bloqueo y la APIlocalStorage
serial .
Sección 3: ¿Cómo adoptar la asincronía?
La forma clásica de lidiar con la asincronía son las cadenas de devolución de llamada.
Supongamos que tiene el siguiente código síncrono:
var result = doSomething();
doSomethingElse(result);
Supongamos que, ahora,
doSomething
es asíncrono.
Entonces esto se convierte en:
doSomething(function(result) {
doSomethingElse(result);
});
Pero, ¿y si es aún más complejo? Digamos que fue:
function doABunchOfThings() {
var intermediate = doSomething();
return doSomethingElse(intermediate);
}
if (doABunchOfThings() == 42) {
andNowForSomethingCompletelyDifferent()
}
Bueno ... En este caso, necesita mover
todo
esto en la devolución de llamada.
return
debe convertirse en una llamada en su lugar.
function doABunchOfThings(callback) {
doSomething(function(intermediate) {
callback(doSomethingElse(intermediate));
});
}
doABunchOfThings(function(result) {
if (result == 42) {
andNowForSomethingCompletelyDifferent();
}
});
Aquí tiene una cadena de devoluciones de llamada:
doABunchOfThings
llama a
doABunchOfThings
inmediatamente, que finaliza, pero en
algún momento más tarde
llama a
doSomethingElse
, cuyo resultado se alimenta a través de otra devolución de llamada.
Obviamente, la estratificación de esto puede ser desordenada. Bueno, nadie dijo que JavaScript es un buen lenguaje. Bienvenido a Callback Hell .
Existen herramientas para hacerlo más manejable, por ejemplo Promises . No los discutiré aquí (quedando sin espacio), pero no cambian la parte fundamental "este código solo se ejecutará más tarde".
Sección TL; DR: ¡absolutamente debo tener el almacenamiento sincrónico, halp!
A veces hay razones legítimas para tener un almacenamiento sincrónico.
Por ejemplo, las llamadas de bloqueo de la API
webRequest
no pueden esperar.
O Callback Hell te va a costar caro.
Lo que puede hacer es tener una
memoria caché
síncrona del
chrome.storage
asíncrono.
Viene con algunos costos, pero no es imposible.
Considerar:
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
Si puede poner TODO su código de inicialización en una función
init()
, entonces tiene esto:
var storageCache = {};
chrome.storage.sync.get(null, function(data) {
storageCache = data;
init(); // All your code is contained here, or executes later that this
});
Para cuando se ejecute el código de tiempo en
init()
, y luego,
cuando ocurra cualquier evento al que se
storageCache
asignado controladores en
init()
, se
storageCache
.
Ha reducido la asincronía a UNA devolución de llamada.
Por supuesto, esto es solo una instantánea de lo que el almacenamiento se ve en el momento de ejecutar
get()
.
Si desea mantener la coherencia con el almacenamiento, debe configurar actualizaciones para
storageCache
través de
eventos
chrome.storage.onChanged
.
Debido a la naturaleza de bucle de evento único de JS, esto significa que la memoria caché solo se actualizará mientras su código no se ejecute, pero en muchos casos eso es aceptable.
Del mismo modo, si desea propagar los cambios en
storageCache
al almacenamiento real, simplemente establecer
storageCache[''key'']
no es suficiente.
storageCache
escribir un
set(key, value)
shim que AMBOS escribe en
storageCache
y programa un
chrome.storage.sync.set
(asíncrono).
Implementarlos se deja como un ejercicio.
Si lograste resolver esto, habrás creado una fuente de errores extraños. Los mensajes se ejecutan de forma asíncrona, lo que significa que cuando envía un mensaje, el resto de su código puede ejecutarse antes de que regrese la función asíncrona. No hay garantía para eso, ya que Chrome es multiproceso y la función de obtención puede retrasarse, es decir, el disco duro está ocupado. Usando su código como ejemplo:
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?");
Por lo tanto, será mejor si usa algo como esto:
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");
});
Si realmente desea devolver este valor, utilice la API de almacenamiento de JavaScript. Esto almacena solo valores de cadena, por lo que debe convertir el valor antes de almacenarlo y después de obtenerlo.
//Setting the value
localStorage.setItem(''disableautoplay'', JSON.stringify(true));
//Getting the value
var a = JSON.stringify(localStorage.getItem(''disableautoplay''));