Utiliser une requête synchrone pour un userscript

a marqué ce sujet comme résolu.

Bonjour,

J’essaye d’écrire un script qui effectue une requête vers mon profil et affiche le code source de mon profil dans la console.
L’intérêt est purement pédagogique, c’est pour me familiariser avec l’objet GM_xmlHttpRequest.

Toute la difficulté, c’est de réaliser une requête synchrone… qui fonctionne !
J’ai l’impression que mon script est OK mais la requête reste malgré tout asynchrone.

Mon script renvoie toujours "Échec de la requête !".
Une idée d’où pourrait venir le problème ?


En cherchant sur des forums, ça parle de récupérer le retour de GM_xmlhttpRequest().
Le problème, c’est que chez moi, le retour ne fonctionne pas et la documentation indique bien undefined comme valeur de retour.

C’est pourquoi je ne pense pas que ce soit la bonne solution pour réaliser une requête synchrone.

Code source du script :
// ==UserScript==
// @name         GreenCheckProfile
// @description  Script pour apprendre à utiliser GM_xmlhttpRequest()
// @match        https://zestedesavoir.com/*
// @grant        GM_xmlhttpRequest
// @connect      zestedesavoir.com
// ==/UserScript==


// Uniquement pour aider au debugage
'use strict';

// Envoie une requête vers 'url' et en récupère le texte brut
// L'objectif est d'avoir une requête synchrone
function myRequest(url, callback) {

    let request = {
        method: "GET",
        url: 'https://zestedesavoir.com/' + url,

        // L'instruction-clef qui ne fonctionne pas :
        synchronous: true,

        onload: function(response) {
            callback(response.responseText);
        }
    };

    GM_xmlhttpRequest(request);
}


(() => {
    let result = '';

    myRequest("membres/voir/Green/", (data) => {
        result = data;
    });

    // Si la requête précédente est synchrone,
    // 'result' contient le texte brut de mon profil

    if (result !== '') {
        console.log('--- Result ---\n' + result);
    }
    else {
        alert('Échec de la requête !');
    }
})();

Si tu utilises une requête synchrone tu n’auras pas de callback.

Le problème est que GreaseMonkey ne renvoie pas la XHR ni un wrapper autour tu ne pourras donc pas récupérer le résultat (ça ressemble plus à une option pour envoyer des infos que pour en recevoir).

Les requêtes synchrones sont globalement à éviter, donc essaie de voir si tu ne peux pas adapter ton code pour de l’asynchrone.

Hum, je vois… Si je dois revenir en asynchrone, je continuerais l’exécution du script juste après result = data;.
Quelque chose comme :

// ==UserScript==
// @name         GreenCheckProfile
// @version      1.2
// @description  Script pour apprendre à utiliser GM_xmlhttpRequest()
// @match        https://zestedesavoir.com/*
// @grant        GM_xmlhttpRequest
// @connect      zestedesavoir.com
// ==/UserScript==


'use strict';

// Envoie une requête asynchrone vers 'url' et en récupère le texte brut
function myRequest(url, callback) {

    let request = {
        method: "GET",
        url: 'https://zestedesavoir.com/' + url,
        onload: function(response) {
            callback(response.responseText);
        }
    };

    GM_xmlhttpRequest(request);
}


(() => {
    let result = '';

    myRequest("membres/voir/Green/", (data) => {
        result = data;
        _continue(); // On ajoute '_' car continue est un mot-clef réservé
    });

    function _continue() {

        if (result !== '') {
            console.log('--- Result ---\n' + result);
        }
        else {
            alert('Échec de la requête !');
        }
    }

})();

Le script s’exécute correctement ! :)
Vous pensez qu’il vaut mieux déplacer _continue() dans le contexte global ?

Sinon tu peux utiliser les promesses et async/await.

function myRequest(url) {
    return new Promise(resolve => {
        let request = {
            method: "GET",
            url: 'https://zestedesavoir.com/' + url,
            onload: function(response) {
                resolve(response.responseText);
            }
        };
        GM_xmlhttpRequest(request);
    }); // création de la promesse
}


(async () => {
    let result = await myRequest("membres/voir/Green/"); // la promesse est synchrone

    if (result !== '') {
        console.log('--- Result ---\n' + result);
    } else {
        alert('Échec de la requête !');
    }
})();

Y’a des cas où les callbacks deviennent pénibles.

+2 -0

Les Promise sont des objets natifs en JS ?

Oui depuis la version ES6. Tout comme async/await.

On peut l’utiliser ainsi :

/* Création de la promesse */
let promise = new Promise((resolve, reject) => {
    /* Simulation d'une opération asynchrone ( = la fonction s'exécute après 100ms) */
    setTimeout(() => {
        /* Test (ici sur un nombre tiré aléatoirement) */
        if(Math.random() > 0.5) {
            resolve(); // la promesse est résolue
        } else {
            reject(); // une erreur est survenue, la promesse est rejetée
        }
    }, 100);
});

/* Attente de la réponse */
promise
    .then(() => { // appel après un resolve
        console.log('SUCCESS');
    })
    .catch(() => { // appel après un reject
        console.error('ERROR');
    });

Et le mot-clef async sert à exécuter tout le code en asynchrone ?

C’est plutôt une convention d’écriture pour l’interpréteur qui va comprendre qu’il doit attendre la fin d’un await avant de continuer son exécution.

On pourrait transformer le code ci-dessus ainsi :

/* Création de la promesse */
let promise = new Promise(resolve => {
    /* Simulation d'une opération asynchrone ( = la fonction s'exécute après 100ms) */
    setTimeout(() => {
        /* Test (ici sur un nombre tiré aléatoirement) */
        if(Math.random() > 0.5) {
            resolve(true); // la promesse est résolue
        } else {
            resolve(false); // une erreur est survenue
        }
    }, 100);
});

(async () => {
    let result = await promise;
    
    if(result) {
        console.log('SUCCESS');
    } else {
        console.error('ERROR');
    }
})();

J’ai choisi dans ce dernier exemple d’utiliser seulement resolve. On peut garder reject mais il faut encapsuler le résultat par un try/catch car reject lève une exception.

+0 -0

Salut,

J’ai lu ton tutoriel (très bien écrit au passage ! :) ) sur les promesses et j’ai écrit, pour m’entraîner, un script chargé de récupérer les profils de quelques membres de ZdS. Tout fonctionne comme attendu, sauf le message « Fin du programme ! » qui s’affiche… tout au début !

Une idée d’où pourrait venir le problème ?

// ==UserScript==
// @name         GreenJS_Promise
// @version      1.0
// @description  Script pour apprendre à utiliser les promesses
// @match        https://zestedesavoir.com/*
// @grant        GM_xmlhttpRequest
// ==/UserScript==


/**
  *  On part d'une petite liste de membres et on souhaite
  *  récupérer le contenu de leurs profils à l'aide de requêtes.
  *
  *  Pour éviter de flood, on décide qu'il faut attendre
  *  3 secondes entre chaque requête (exécution synchrone).
  */


'use strict';
let membres = ['Green', 'Yarflam', 'viki53'];


(async () => {
    
    /**
      *  Ici, async et await sont utilisés afin
      *  que le script affiche "Fin du programme"
      *  après l'exécution des 3 requêtes
      */
    await callPromise(0);
    console.log('Fin du programme !');

})();


/**
  *  Fonction récursive chargée d'appeler
  *  une requête à la fois, avec 3 sec. de délai
  */
function callPromise(i) {
    loadProfile(membres[i]).then(data => {
        
        console.log('*** Profil de ' + membres[i] + ' ***');
        console.log(data.substring(0, 200).trim());

        if (++i < membres.length) {
            setTimeout(callPromise, 3000, i);
        }
    });
}


/**
  *   Fonction chargée de récupérer le contenu d'un profil
  */
function loadProfile(member) {
    return new Promise(resolve => {
        GM_xmlhttpRequest({
            method: "GET",
            url: 'https://zestedesavoir.com/membres/voir/' + member,
            onload: function(response) {
                resolve(response.responseText);
            }
        });
    });
}

Alors oui une spécificité qui rend parfois cette méthode plus fastidieuse, c’est qu’il faut penser à "resynchroniser" toutes les fonctions & méthodes intermédiaires.

Il te faut modifier ta fonction intermédiaire callPromise en ajoutant async/await :

Erratum : je me rend compte que tu ne peux pas en faite avec ta fonction callPromise. Elle s’appelle elle-même plusieurs fois. Tu dois plutôt utiliser une boucle. Avec par exemple Promise.all() (je te laisse regarder la doc ;) ).

+0 -0

Salut, ici j’ai un petit souci :

Pourtant, ma fonction semble bien définie.
C’est une erreur assez récurrente dans mes scripts, une idée d’où pourrait venir le problème ?

// ==UserScript==
// @name         GreenJS_Test
// @version      1
// @description  Script de test
// @match        https://zestedesavoir.com/*
// ==/UserScript==


function btn_function() {
    alert('test');
}


(async () => {

    document.write("<input type='button' onclick='btn_function();' value='Valider'>");

})();

J’ai par exemple ce cas lorsque je veux ajouter un bouton à la page web et y associer une fonction lorsqu’on clique dessus.

En fait, je me rends compte qu’il est possible d’ajouter un événement à un élément déjà existant, mais impossible d’ajouter un événement à un bouton qu’on ajoute dans la page, par exemple :

// ==UserScript==
// @name         GreenJS_Test
// @version      1.2
// @description  Script de test
// @match        https://zestedesavoir.com/*
// ==/UserScript==


function btn_function() {
    alert('test');
}


(async () => {

    let message = document.getElementById('p226412');

    if (message) {
        message.innerHTML = "<input type='button' onclick='btn_function();' value='Valider'>";
    }
})();

Ici, la variable message pointe vers le message de viki53.

Or, j’aimerais bien ajouter un bouton, puis lui associer une fonction à exécuter lorsqu’on clique dessus. Des idées ?

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte