web-dev-qa-db-fra.com

Renvoi de la valeur de l'API de stockage Chrome sans fonction

Je travaille depuis deux jours avec le stockage asynchrone chromé. Cela fonctionne "bien" si vous avez une fonction. (Comme ci-dessous):

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

Mon problème est que je ne peux pas utiliser une fonction avec ce que je fais. Je veux juste le retourner, comme peut le faire LocalStorage. Quelque chose comme:

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

ou

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

J'ai essayé un million de combinaisons, même en définissant une variable publique et en définissant que: 

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

Rien ne fonctionne. Tout retourne indéfini à moins que le code qui le référence soit à l'intérieur de la fonction get, et cela m'est inutile. Je veux juste pouvoir retourner une valeur en tant que variable. 

Est-ce seulement possible?

EDIT: Cette question n'est pas un doublon, permettez-moi d'expliquer pourquoi:

1: Il n'y a pas d'autres posts qui demandent cela spécifiquement (j'ai passé deux jours à regarder d'abord, juste au cas où).

2: Ma question n'est toujours pas répondue. Oui, Chrome Storage est asynchrone et oui, il ne renvoie aucune valeur. C'est le problème. Je vais élaborer ci-dessous ...

Je dois pouvoir obtenir une valeur stockée en dehors de la fonction chrome.storage.sync.get. Je ne peux pas utiliser localStorage, car il est spécifique à l'URL et les mêmes valeurs ne sont pas accessibles depuis la page browser_action de l'extension chrome et le fichier background.js. Je ne peux pas stocker une valeur avec un script et y accéder avec un autre. Ils sont traités séparément.

Ma seule solution consiste donc à utiliser Chrome Storage. Il doit y avoir un moyen d'obtenir la valeur d'un élément stocké et de le référencer en dehors de la fonction get. Je dois vérifier dans une déclaration si.

Tout comme le stockage local peut faire

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

Il doit y avoir un moyen de faire quelque chose dans le sens de

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

Je réalise que ce ne sera pas aussi simple, mais c'est la meilleure façon de l'expliquer.

Chaque message que je vois dit de le faire de cette façon:

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.
9
Kyle Hickey

D'accord, d'accord, vous obtenez votre réponse personnalisée à votre question. L'explication de la raison pour laquelle vous ne pouvez pas rester asynchrone sera toujours longue à 90%, mais supportez-moi, cela vous aidera en général. Je promets qu'il y a quelque chose de pertinent pour chrome.storage à la fin.

Avant même de commencer, je vais répéter les liens canoniques pour ceci:

Discutons donc de l’asynchonicité de JS.

Section 1: De quoi s'agit-il?

Le premier concept à couvrir est environnement d'exécution. JavaScript est en quelque sorte intégré à un autre programme qui contrôle son déroulement, en l'occurrence Chrome. Tous les événements qui se produisent (minuteries, clics, etc.) proviennent de l'environnement d'exécution. Le code JavaScript enregistre les gestionnaires pour les événements, qui sont mémorisés par le moteur d'exécution et sont appelés selon le cas.

Deuxièmement, il est important de comprendre que JavaScript est mono-thread. Il y a un seul (boucle d'événement} _ maintenu par l'environnement d'exécution; Si un autre code est en cours d'exécution lorsqu'un événement se produit, cet événement est placé dans une file d'attente à traiter lorsque le code actuel se termine .

Jetez un oeil à ce code:

var clicks = 0;

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

Alors, que se passe-t-il ici? Lorsque ce code s'exécute, lorsque l'exécution atteint .addEventListener, les événements suivants se produisent: l'environnement d'exécution est averti que lorsque l'événement se produit (vous cliquez sur element), il doit appeler la fonction de gestionnaire.

Il est important de comprendre (même si dans ce cas c'est assez évident) que la fonction est pas exécutée à ce stade. Il ne lancera que plus tard, lorsque cet événement se produira. L'exécution continue dès que le moteur d'exécution reconnaît 'Je vais courir (ou "rappeler", d'où le nom "callback") ceci quand cela se produit.' Si someMoreCode() essaie d'accéder à clicks, ce sera 0 , pas 1.

C'est ce qu'on appelle asynchronicité, car c'est quelque chose qui se produira en dehors du flux d'exécution actuel.

Section 2: Pourquoi est-ce nécessaire ou pourquoi les API synchrones sont-elles en train de disparaître?

Maintenant, une considération importante. Supposons que someMoreCode() est en réalité un morceau de code très long. Que se passera-t-il si un événement de clic s'est produit pendant qu'il est toujours en cours d'exécution?

JavaScript n'a pas de concept d'interruption. Runtime verra que du code est en cours d'exécution et mettra l'appel du gestionnaire d'événements dans la file d'attente. Le gestionnaire ne s'exécutera pas avant la fin de someMoreCode().

Bien qu'un gestionnaire d'événements de clic soit extrême en ce sens que le clic n'est pas garanti, cela explique pourquoi vous ne pouvez pas attendre le résultat d'une opération asynchrone . Voici un exemple qui ne fonctionnera pas:

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

Vous pouvez cliquer sur le contenu de votre coeur, mais le code qui incrémenterait clicks attend patiemment la fin de la boucle (sans fin). Oops.

Notez que ce morceau de code ne gèle pas seulement ce morceau de code: chaque événement n'est plus géré pendant que nous attendons , car il n'y a qu'un seul file d'attente/thread d'événement. En JavaScript, il n’existe qu’un moyen de laisser les autres gestionnaires s’acquitter de leur tâche: mettre fin au code actuel et laisser le runtime savoir comment appeler lorsque quelque chose que nous souhaitons se produit.


C'est pourquoi le traitement asynchrone est appliqué à une autre classe d'appels qui:

  • demande à l'exécution, et non à JS, de faire quelque chose (accès disque/réseau par exemple)
  • sont garantis pour se terminer (que ce soit en succès ou en échec)

Allons avec un exemple classique: AJAX appelle. Supposons que nous voulions charger un fichier à partir d'une URL.

  • Supposons que sur notre connexion actuelle, le moteur d'exécution puisse demander, télécharger et traiter le fichier sous la forme utilisable dans JS en 100 ms.
  • Sur un autre point, c’est un peu pire, cela prendrait 500 ms.
  • Et parfois, la connexion est vraiment mauvaise, ainsi le moteur d’exécution attendra 1000 ms et abandonnera avec un délai d’attente.

Si nous devions attendre la fin de l'opération, nous aurions un délai variable, imprévisible et relativement long. En raison du fonctionnement de JS Waiting, tous les autres gestionnaires (par exemple, l'interface utilisateur) ne feraient pas leur travail pendant ce délai, ce qui aurait pour effet de bloquer la page.

Sonne familier? Oui, c'est exactement comme cela que XMLHttpRequest synchrone fonctionne . Au lieu d'une boucle while(1) dans le code JS, cela se produit essentiellement dans le code d'exécution, car JavaScript ne permet pas à un autre code de s'exécuter pendant qu'il attend.

Oui, cela permet une forme de code familier:

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

Mais à un coût terrible, de tout ce qui gèle. Un coût si terrible qu’en fait, les navigateurs modernes le considèrent comme obsolète. Voici un discussion sur le sujet sur MDN .


Regardons maintenant localStorage. Il correspond à la description de "appel final au runtime", et pourtant il est synchrone. Pourquoi?

Pour le dire simplement: raisons historiques (c’est une spécification très ancienne).

Bien que cela soit certainement plus prévisible qu'une requête réseau, localStorage a toujours besoin de la chaîne suivante:

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

C'est une chaîne d'événements complexe et l'ensemble du moteur de JS doit être mis en pause. Ceci conduit à ce qui est considéré comme une performance inacceptable .


Désormais, les API Chrome sont entièrement conçues pour la performance. Vous pouvez toujours voir des appels synchrones dans les API anciennes _ telles que chrome.extension. De plus, certains appels sont gérés dans JS (et ont donc un sens en tant que synchrones), mais chrome.storage est (relativement) nouveau.

En tant que tel, il embrasse le paradigme "Je reconnais votre appel et je reviendrai avec des résultats, faites maintenant quelque chose d'utile pendant ce temps" s'il y a un retard dans l'exécution de quelque chose avec runtime. Il n'existe pas de version synchrone de ces appels, contrairement à XMLHttpRequest.

Citation de la documentation:

C'est [chrome.storage] asynchrone avec les opérations de lecture et d'écriture en bloc, et donc plus rapide que l'API de blocage et série localStorage.

Section 3: Comment adopter l'asynchronicité?

Les chaînes de rappel constituent le moyen classique de traiter l'asynchronicité.

Supposons que vous ayez le code synchrone suivant:

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

Supposons que, maintenant, doSomething est asynchrone. Alors ceci devient:

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

Mais si c'est encore plus complexe? Dis que c'était:

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

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

Bien .. Dans ce cas, vous devez déplacer tout ceci dans le rappel. return doit devenir un appel à la place.

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

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

Ici vous avez une chaîne de callbacks: doABunchOfThings appelle doSomething immédiatement, ce qui se termine, mais quelque temps plus tard appelle doSomethingElse, dont le résultat est transmis à if par un autre rappel.

De toute évidence, la superposition de ceci peut être compliquée. Eh bien, personne n’a dit que JavaScript était une bonne langue .. Bienvenue dans Callback Hell .

Il existe des outils pour le rendre plus facile à gérer, par exemple Promises . Je ne vais pas en discuter ici (manque d’espace), mais ils ne modifient pas la partie fondamentale "ce code ne fonctionnera que plus tard".

Section TL; DR: Je dois absolument avoir le stockage synchrone, halp!

Parfois, il existe des raisons légitimes d'avoir un stockage synchrone. Par exemple, les appels de blocage webRequest de l'API ne peuvent pas attendre. Ou Callback Hell va vous coûter cher.

Ce que vous pouvez faire est d’avoir un cache synchrone du chrome.storage asynchrone. Cela entraîne des coûts, mais ce n'est pas impossible.

Considérer:

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 vous pouvez mettre TOUT votre code d’initialisation dans une fonction init(), alors vous avez ceci:

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

Par le code temporel dans init() est exécuté, et ensuite lorsqu'un événement attribué à des gestionnaires dans init() survient , storageCache sera renseigné. Vous avez réduit l’asynchronisme à un rappel.

Bien entendu, il ne s'agit que d'un instantané de ce à quoi ressemble le stockage au moment de l'exécution de get(). Si vous souhaitez maintenir la cohérence avec le stockage, vous devez configurer les mises à jour sur storageCache via les événements chrome.storage.onChanged . En raison de la nature en boucle à événement unique de JS, cela signifie que le cache ne sera mis à jour que si votre code ne s'exécute pas, mais dans de nombreux cas, cela est acceptable.

De même, si vous souhaitez propager les modifications apportées à storageCache au stockage réel, il suffit de définir storageCache['key'] ne suffit pas. Vous devez écrire un shim set(key, value) que BOTH écrit dans storageCache et planifier un chrome.storage.sync.set (asynchrone).

Leur mise en œuvre reste un exercice.

19
Xan
  1. chrome.storage.sync.get n'a pas de valeur renvoyée, ce qui explique pourquoi vous obtiendrez undefined en appelant quelque chose comme 

    var a = chrome.storage.sync.get({"disableautoplay": true});
    
  2. chrome.storage.sync.get est également une méthode asynchrone , ce qui explique pourquoi, dans le code suivant, a serait undefined sauf si vous y accédez à l'aide de la fonction de rappel. 

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

Si vous parveniez à résoudre ce problème, vous auriez créé une source de bugs étranges. Les messages sont exécutés de manière asynchrone, ce qui signifie que lorsque vous envoyez un message, le reste de votre code peut être exécuté avant le retour de la fonction asynchrone. Il n’ya aucune garantie à ce sujet puisque chrome est multi-threaded et que la fonction get peut être retardée, c’est-à-dire que le disque dur est occupé. En utilisant votre code comme exemple:

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

Donc ce sera mieux si vous utilisez quelque chose comme ceci:

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 vous voulez vraiment renvoyer cette valeur, utilisez l'API de stockage javascript. Cela ne stocke que les valeurs de chaîne. Vous devez donc convertir la valeur avant de la stocker et après l'avoir obtenue.

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

//Getting the value
var a = JSON.stringify(localStorage.getItem('disableautoplay'));
0
ze_iliasgr