Restituzione del valore dell’API di archiviazione di Chrome senza funzione

Negli ultimi due giorni ho lavorato con lo storage asincrono del cromo. Funziona “bene” se hai una funzione. (Come sotto):

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

Il mio problema è che non posso usare una funzione con quello che sto facendo. Voglio solo restituirlo, come può fare LocalStorage. Qualcosa di simile a:

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

o

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

Ho provato un milione di combinazioni, anche impostando una variabile pubblica e impostando questo:

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

Niente funziona. Tutto torna indefinito a meno che il codice che fa riferimento sia all’interno della funzione del get, e questo è inutile per me. Voglio solo essere in grado di restituire un valore come variabile.

È ansible?

EDIT: Questa domanda non è un duplicato, per favore mi permetta di spiegare perché:

1: Non ci sono altri post che lo chiedono specificatamente (ho trascorso due giorni guardando prima, per ogni evenienza).

2: La mia domanda non ha ancora risposta. Sì, Chrome Storage è asincrono e sì, non restituisce un valore. Questo è il problema. Elaborerò di seguito …

Devo essere in grado di ottenere un valore memorizzato al di fuori della funzione chrome.storage.sync.get. I -cannot- utilizza localStorage, in quanto è specifico per l’URL, e non è ansible accedere agli stessi valori sia dalla pagina browser_action dell’estensione chrome, sia da background.js. Non riesco a memorizzare un valore con uno script e accedervi con un altro. Sono trattati separatamente.

Quindi la mia unica soluzione è usare Chrome Storage. Ci deve essere un modo per ottenere il valore di un object memorizzato e farne riferimento all’esterno della funzione get. Devo controllarlo in una dichiarazione if.

Proprio come può fare localStorage

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

Ci deve essere un modo per fare qualcosa sulla falsariga di

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

Mi rendo conto che non sarà così semplice, ma è il modo migliore per spiegarlo.

Ogni post che vedo dice di farlo in questo modo:

 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. 

Ok, va bene, hai la risposta su misura alla tua domanda. Rimarrà comunque la spiegazione del 90% per cui non puoi andare in giro asincrono, ma tieni con me – ti aiuterà in generale. Prometto che alla fine c’è qualcosa di pertinente in chrome.storage .

Prima ancora di iniziare, ripeterò i link canonici per questo:

  • Dopo aver chiamato chrome.tabs.query, i risultati non sono disponibili
    (Chrome specifico, ottima risposta di RobW, probabilmente più facile da capire )
  • Perché la mia variabile è inalterata dopo averla modificata all’interno di una funzione? – Riferimento di codice asincrono ( riferimento canonico generale su ciò che stai chiedendo)
  • Come posso restituire la risposta da una chiamata asincrona?
    (una domanda canonica più antica ma non meno rispettata su JS asincrono)
  • Non sai JS: Async & Performance (ebook su JS asincronicity)

Quindi, parliamo dell’asinconicità di JS.

Sezione 1: che cos’è?

Il primo concetto da trattare è l’ambiente di runtime . JavaScript è, in un certo senso, incorporato in un altro programma che ne controlla il stream di esecuzione – in questo caso, Chrome. Tutti gli eventi che si verificano (timer, clic, ecc.) Provengono dall’ambiente di runtime. Il codice JavaScript registra i gestori per gli eventi, che vengono ricordati dal runtime e vengono chiamati come appropriato.

In secondo luogo, è importante capire che JavaScript è a thread singolo. Esiste un singolo ciclo di eventi gestito dall’ambiente di runtime; se c’è un altro codice in esecuzione quando si verifica un evento, quell’evento viene inserito in una coda da elaborare quando termina il codice corrente .

Dai un’occhiata a questo codice:

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

Quindi, cosa sta succedendo qui? Mentre questo codice viene eseguito, quando l’esecuzione raggiunge .addEventListener , accade quanto segue: l’ambiente di runtime viene informato che quando si verifica l’evento (l’ element viene cliccato), dovrebbe chiamare la funzione di gestore.

È importante capire (anche se in questo caso particolare è abbastanza ovvio) che la funzione non viene eseguita a questo punto. Funzionerà solo più tardi , quando succederà l’evento. L’esecuzione continua non appena il runtime conferma “eseguirò (o” richiamerà “, da qui il nome” callback “) quando ciò accade”. Se someMoreCode() tenta di accedere ai clicks , sarà 0 , non 1 .

Questo è ciò che viene chiamato asincronismo , poiché questo è qualcosa che accadrà al di fuori del stream di esecuzione corrente.

Sezione 2: Perché è necessario o perché le API sincrone si stanno estinguendo?

Ora, una considerazione importante. Supponiamo che someMoreCode() sia in realtà una parte di codice molto lunga. Che cosa succederà se un clic è successo anche mentre è ancora in esecuzione?

JavaScript non ha alcun concetto di interrupt. Runtime vedrà che è in esecuzione il codice e metterà la chiamata del gestore di eventi in coda. Il gestore non verrà eseguito prima che someMoreCode() finisca completamente.

Mentre un gestore di eventi click è estremo nel senso che non è garantito il clic, questo spiega perché non si può attendere il risultato di un’operazione asincrona . Ecco un esempio che non funzionerà:

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

Puoi fare clic sul contenuto del tuo cuore, ma il codice che incrementerebbe i clicks aspetta pazientemente che il ciclo (non terminante) termini. Ops.

Nota che questo pezzo di codice non solo blocca questo pezzo di codice: ogni singolo evento non viene più gestito mentre attendiamo , perché c’è solo un evento coda / thread. C’è solo un modo in JavaScript per consentire agli altri handler di fare il loro lavoro: terminare il codice corrente e lasciare che il runtime sappia cosa chiamare quando succede qualcosa che vogliamo .


Questo è il motivo per cui il trattamento asincrono viene applicato a un’altra class di chiamate che:

  • richiede al runtime, e non a JS, di fare qualcosa (disco / accesso alla rete per esempio)
  • sono garantiti per terminare (in caso di successo o fallimento)

Andiamo con un esempio classico: chiamate AJAX. Supponiamo di voler caricare un file da un URL.

  • Diciamo che sulla nostra attuale connessione, il runtime può richiedere, scaricare ed elaborare il file nel modulo che può essere utilizzato in JS in 100 ms.
  • Su un’altra connessione, è un po ‘peggio, ci vorrebbero 500ms.
  • E a volte la connessione è davvero pessima, quindi il runtime attenderà 1000 ms e rinuncerà a un timeout.

Se dovessimo attendere fino al completamento, avremmo un ritardo variabile, imprevedibile e relativamente lungo. A causa di come JS in attesa funziona, tutti gli altri gestori (es. UI) non farebbero il loro lavoro per questo ritardo, portando a una pagina bloccata.

Suona familiare? Sì, è esattamente come funziona XMLHttpRequest sincrono . Invece di un ciclo while(1) nel codice JS, si verifica essenzialmente nel codice runtime, poiché JavaScript non può consentire l’esecuzione di altro codice mentre è in attesa.

Sì, questo consente una forma familiare di codice:

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

Ma a un terribile, terribile costo di tutto il congelamento. Un costo così terribile che, di fatto, i browser moderni lo considerano deprecato. Ecco una discussione sull’argomento su MDN .


Ora diamo un’occhiata a localStorage . Corrisponde alla descrizione di “terminare la chiamata al runtime”, eppure è sincrono. Perché?

Per dirla semplicemente: ragioni storiche (è una specifica molto vecchia).

Mentre è certamente più prevedibile di una richiesta di rete, localStorage ancora bisogno della seguente catena:

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

È una catena complessa di eventi e l’intero motore JS deve essere sospeso per questo. Ciò porta a quella che è considerata una prestazione inaccettabile .


Ora, le API di Chrome sono, da zero, progettate per le prestazioni. Puoi ancora vedere alcune chiamate sincrone in API precedenti come chrome.extension e ci sono chiamate che vengono gestite in JS (e quindi hanno senso come sincrono) ma chrome.storage è (relativamente) nuovo.

Come tale, abbraccia il paradigma “Riconosco la tua chiamata e tornerò con i risultati, ora faccio qualcosa di utile nel frattempo” se c’è un ritardo nel fare qualcosa con il runtime. Non ci sono versioni sincrone di quelle chiamate, diversamente da XMLHttpRequest.

Citando i documenti:

È [ chrome.storage ] asincrono con le operazioni di lettura e scrittura in blocco , e quindi più veloce rispetto all’API localStorage blocco e seriale .

Sezione 3: Come abbracciare l’asincronicità?

Il modo classico di trattare l’asincronicità sono le catene di callback.

Supponiamo di avere il seguente codice sincrono:

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

Supponiamo che, ora, doSomething sia asincrono. Allora questo diventa:

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

Ma cosa succede se è ancora più complesso? Di ‘che era:

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

Bene .. In questo caso è necessario spostare tutto questo nella richiamata. return deve diventare invece una chiamata.

 function doABunchOfThings(callback) { doSomething(function(intermediate) { callback(doSomethingElse(intermediate)); }); } doABunchOfThings(function(result) { if (result == 42) { andNowForSomethingCompletelyDifferent(); } }); 

Qui c’è una catena di callback: doABunchOfThings chiama immediatamente doABunchOfThings , che termina, ma qualche volta dopo chiama doSomethingElse , il cui risultato viene inviato a if tramite un altro callback.

Ovviamente, la stratificazione di questo può diventare disordinata. Beh, nessuno ha detto che JavaScript è una buona lingua … Benvenuti in Callback Hell .

Esistono strumenti per renderlo più gestibile, ad esempio Promises . Non ne parlerò qui (esaurendo lo spazio), ma non cambiano la parte fondamentale “questo codice verrà eseguito solo più tardi”.

Sezione TL; DR: Devo assolutamente avere lo storage sincrono, halp!

A volte ci sono motivi legittimi per avere una memoria sincrona. Ad esempio, le webRequest blocco dell’API webRequest non possono attendere. O Callback Hell ti costerà caro.

Quello che puoi fare è avere una cache sincrona del chrome.storage asincrono. Viene fornito con alcuni costi, ma non è imansible.

Prendere in considerazione:

 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 

Se puoi inserire TUTTO il tuo codice di inizializzazione in una funzione init() , allora hai questo:

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

Con il codice temporale in init() viene eseguito e in seguito, quando si verifica un evento a cui sono stati assegnati handler in init() , verrà popolato storageCache . Hai ridotto l’asincronia a UNA richiamata.

Naturalmente, questa è solo un’istantanea di ciò che l’archiviazione guarda al momento dell’esecuzione di get() . Se si desidera mantenere la coerenza con lo spazio di archiviazione, è necessario impostare aggiornamenti su storageCache tramite chrome.storage.onChanged eventi modificati . A causa della natura del ciclo a singolo evento di JS, ciò significa che la cache verrà aggiornata solo mentre il codice non viene eseguito, ma in molti casi è accettabile.

Allo stesso modo, se si desidera propagare le modifiche a storageCache allo storage reale, l’impostazione di storageCache['key'] non è sufficiente. Dovresti scrivere uno shim set(key, value) che ENTRAMBI scrive su storageCache e pianifica un chrome.storage.sync.set (asincrono).

Implementare quelli è lasciato come esercizio.

  1. chrome.storage.sync.get non ha valori restituiti, il che spiega il motivo per cui si otterrebbe undefined quando si chiama qualcosa di simile

     var a = chrome.storage.sync.get({"disableautoplay": true}); 
  2. chrome.storage.sync.get è anche un metodo asincrono , il che spiega perché nel seguente codice a sarebbe undefined meno che non lo si acceda all’interno della funzione di callback.

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

Se riuscirai a risolverlo, avrai creato una fonte di strani bug. I messaggi vengono eseguiti in modo asincrono, il che significa che quando si invia un messaggio il resto del codice può essere eseguito prima che la funzione asincrona ritorni. Non c’è garanzia per questo poiché chrome è multi-thread e la funzione get può ritardare, ovvero hdd è occupato. Usando il tuo codice come esempio:

 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?"); 

Quindi sarà meglio se usi qualcosa del genere:

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

Se si desidera davvero restituire questo valore, utilizzare l’API di archiviazione javascript. Questo memorizza solo i valori di stringa in modo da dover trasmettere il valore prima di archiviare e dopo averlo ottenuto.

 //Setting the value localStorage.setItem('disableautoplay', JSON.stringify(true)); //Getting the value var a = JSON.stringify(localStorage.getItem('disableautoplay'));