Ajax: async or not async? callbacks, promesses

,

Je me convertis vite :sweat_smile: : pour mon cas imbriqué les await sont effectivement plus pratiques quand je pars de ton exemple.

En plus, pour revenir aux promesses, c’est bien plus rapide puisque je ne perds plus de temps avec les setTimeout().

Edit:
Yes mon for asynchrone fonctionne, avec des await (merci) :slight_smile:

  let a=[]
  let b=[]
  let c=[]
  (async function () {
          for (var ii in mondict) {

            a= await apiGlobal.getMonApi(
              mondict[ii].monid
            ); 

            b= [
              ...b.map((x) => Object.assign({}, x)),
              ...a.data,
            ];

            c.push(
              Object.assign(
                {},
                ...Object.values(mondict[ii]).map((x, i, a) =>
                  ["monid", "moncode"].indexOf(
                    Object.keys(mondict[0])[i]
                  ) > -1
                    ? Object.assign(
                        {},
                        {
                          [Object.keys(mondict[0])[i]]: a[i],
                          a_courant: [
                            ...Object.values(a.data).map(
                              (x) => x.monautreid
                            ),
                          ],
                        }
                      )
                    : Object.assign({})
                )
              )
            );
          } //fin for

        })().then(() => {
          // enlève les doublons de b et c
          // Tri de c

          // met à jour ma variable reactive Vue branchée sur un v-model
          REACT.c = [...c]; 
          // met à jour ma variable reactive Vue branchée sur un v-model
          REACT.b= [...b]; 
          // met à jour ma variable reactive Vue branchée sur un v-show 
          //   d'un DOM table  pointant sur le v-model  REACT.b
          REACT.b_charge = true; 
        });
      }
  }

Édit 2:
Quand je vois l’utilisation de cette fonction asynchrone, il doit sans doute être possible de gérer les appels datatable.net. Mais bon comme ça marche avec setTimeOut() je le modifierai plus tard.

GetMonApi().then((datas_params) => {
        setTimeout(function () {
           $("#MaTable").DataTable({ bla  datas_params })
        }, 250);
})

deviendra sans doute

(async function () {
    return await GetMonApi()
 })().then((aaa) => {
    $("#MaTable").DataTable({ bla  aaa })
 })
1 « J'aime »

Est-ce que ces 2 écritures ci-dessous sont complètement interchangeables ?

(async function () {
   a=await PromiseGetMonApiA()
   b=await PromiseMaVariableReactiveVue

   return [a,b]
 })().then((resp) => {   
    AA=FctA(resp.a)
    FctB(AA, resp.b)
 })

Et

a=await PromiseGetMonApiA()
b=await PromiseMaVariableReactiveVue

Promise.all([a,b]).then((resp) => {
    AA=FctA(resp.a)
    FctB(AA, resp.b)
 })

Et qu’en est-il de l’assertion ci-dessous dans ces 2 cas ?

Dans le 2e cas sans fonction contenante asynchrone avant il y a bien une différence ? Ou alors cette fonction contenante ne sert que dans le cas où je veux faire plein de trucs comme mon for asynchrone mais est superflu dans les cas plus simples ?

Édit :

Et le 2e cas peut aussi s’écrire comme ça ?

a=await PromiseGetMonApiA()
b=await PromiseMaVariableReactiveVue

resp=Promise.all([a,b])

AA=FctA(resp.a)
FctB(AA, resp.b)

Mais comment se passe les relations de promesses, notamment les exceptions, pour les executions de FctA() et FctB() ?

Ma crainte est peut-être infondée, mais j’ai l’impression que dans la dernière écriture sans fonction contenante ni avant les promesses, ni après, chacun vit sa vie.

Je pense qu’il y a une mécomprehension de ce que fait await.
Quand tu utilises await sur une promesse, tu attends que la promesse se termine et tu récupère le résultat, le résultat n’est plus une promesse, c’est le même usage que quand tu utilises then, c’est juste la syntaxe qui change.
Si tu await a et b, ce que tu récupère n’est pas une promesse, ce n’est pas la peine de les gérer comme des promesses, ni en les mettant dans un Promise.all, ni dans un promise.resolve, ni dans un then.

Par exemple:

a=await PromiseGetMonApiA()
b=await PromiseMaVariableReactiveVue

resp=Promise.all([a,b])

AA=FctA(resp.a)
FctB(AA, resp.b)

a et b ne sont pas des promesses à cause du mot clé await, pas besoin donc de les gérer dans Promise.all. En revanche resp est une promesse parce que Promise.all renvoit une promesse, il faut que tu utilises await ou then, sinon t’es fonctions fctA et fctB risquent de mal fonctionner.

Note que dans ton exemple, a et b sont récupérés successivement, tu attend a, puis tu attends b, ça marche bien mais c’est peut être un peu plus lent que si tu les lançait en parallèle.

C’est vrai que c’est compliqué, un truc comme ça c’est mieux d’expliquer en live, par forum c’est plus difficile.

Heuuu :sweat_smile:

Ok ok. :slight_smile:

Oui, oui, pas de souci: là j’ai des besoins en série, l’un FctA() est absolument nécessaire d’être résolu pour la fonction suivante FctB() qui en a besoin. Et ce de façon itératif (mon for en fait).

Et donc cette forme dont tu parles ci-dessous convient mieux à mon besoin, mais il ne faut pas perdre de vue les cas d’utilisations de Promise.all().

1 « J'aime »

Tiens, le mieux c’est encore un exemple qui marche et que tu peux rejouer pour voir comment ça fonctionne.
J’ai fait 4 exemple: le premier avec then, le second avec then et Promise.all, le troisième avec await et le quatrième avec await et Promise.all. (et un bonus avec des appels à un service dans une boucle for)

J’ai essayé de coller à tes problématiques, j’espère qu’avec ça tu auras tout. :slight_smile:

async function getRandomNumber() {
	const res = await fetch(`https://svelte.dev/tutorial/random-number`);
	const text = await res.text();

	if (res.ok) {
		return parseInt(text,10);
	} else {
		throw new Error(text);
	}
}

function FctA(x) {
	return x*x;  
}

function FctB(x, y) {
	return x+y;  
}

function FctC(arr) {
  	return arr.reduce((acc, current) => acc + current, 0);
}

function exampleThen() {
  let global_a;
  let global_aa;
  let global_b;
  let global_bb;
  
  getRandomNumber()
  .then(function(local_a){
    global_a = local_a;
    return getRandomNumber();
  })
  .then(function(local_b){
    global_b = local_b;
    global_aa = FctA(global_a);
    global_bb = FctB(global_aa, global_b);

    console.log('exampleThen : ', global_a, global_b, global_aa, global_bb);
  })
  .catch(function(error){
    console.log('exampleThen error :',error);
  });
}

function exampleThenPromiseAll() {
  let global_a;
  let global_aa;
  let global_b;
  let global_bb;
  
  let promiseA = getRandomNumber();
  let promiseB = getRandomNumber();
  
  Promise.all([promiseA,promiseB])
  .then(function([local_a,local_b]){
    global_a = local_a;
    global_b = local_b;
    global_aa = FctA(global_a);
    global_bb = FctB(global_aa, global_b);
    console.log('exampleThenPromiseAll : ', global_a, global_b, global_aa, global_bb);
  })
  .catch(function(error){
    console.log('exampleThenPromiseAll error :',error);
  });
}

async function exampleAwait(){
  try {
    let a = await getRandomNumber();
    let b = await getRandomNumber();
    let aa = FctA(a);
    let bb = FctB(aa, b);

    console.log('exampleAwait : ', a, b, aa, bb);
  } catch (error) {
    console.log('exampleAwait error :',error);
  }
}

async function exampleAwaitPromiseAll(){
  try {
    let promiseA = getRandomNumber();
    let promiseB = getRandomNumber();
    let [a, b] = await Promise.all([promiseA, promiseB]);
    let aa = FctA(a);
    let bb = FctB(aa, b);

    console.log('exampleAwaitPromiseAll : ', a, b, aa, bb);
  } catch (error) {
    console.log('exampleAwaitPromiseAll error :',error);
  }
}

async function exampleAwaitPromiseAllFor(){
  try {
    let arrayPromise = []
    for (let i=0 ; i<3 ; i++){
    	arrayPromise[i] = getRandomNumber();  
    }
    
    let arrayRandom = await Promise.all(arrayPromise);
    let cc = FctC(arrayRandom);

    console.log('exampleAwaitPromiseAllFor : ', arrayRandom, cc);
  } catch (error) {
    console.log('exampleAwaitPromiseAllFor error :',error);
  }
}
  
exampleThen();
exampleThenPromiseAll();
exampleAwait();
exampleAwaitPromiseAll();
exampleAwaitPromiseAllFor();

1 « J'aime »

Merci pour tout ces scénarios :+1: . J’ai testé et regardé cela. :slight_smile:

Pourquoi exampleAwaitPromiseAllFor() fonctionne telle quelle mais pas avec une attente comme le code ci-dessous. Je pensais que c’était ce qu’une promesse était censé faire. Et là un callback fonctionnerait non ? (Mais modifié dans la suite de mon message, et ça marche autrement)

    for (let i=0 ; i<2 ; i++){
       setTimeout(function timer() {
    	     arrayPromise[i] =  getRandomNumber();  
       }, i * 3000);
    }

Après mon attente est peut-être artificielle et pas compatible avec l’asynchrone. Je voulais simuler le fait dans le cas où ça n’arrive pas tout de suite. Dans ce cas j’ai bien pour arrayRandom fulfilled mais value undefined et pour cc undefined.

Mais bon notre discussion peut durer indéfiniment :smiley: Je cherchais simplement à tordre ton exemple pour tester.

C’est bon, j’ai trouvé un « bon » wait là :

Et là ma petite modif d’essai marche.

async function getRandomNumber() {
	const res = await fetch(`https://www.randomnumberapi.com/api/v1.0/random?min=100&max=1000&count=1`, 
        {mode: 'cors'}
    );
	const text = await res.json();
    console.log(text[0])
	if (res.ok) {
		return parseInt(text[0],10);
	} else {
		throw new Error(text);
	}
}

function FctC(arr) {
  	return arr.reduce((acc, current) => acc + current, 0);
}

function mywait (Tab)  {
 return new Promise(resolve =>
    setTimeout(() => resolve(Tab), 5000)
  );
}

async function exampleAwaitPromiseAllFor(){
  try {
    let arrayPromise = []
    for (let i=0 ; i<5 ; i++){
       arrayPromise[i] =await mywait( getRandomNumber())
    }
    let arrayRandom = await Promise.all(arrayPromise);
    let cc = FctC(arrayRandom);

    console.log('exampleAwaitPromiseAllFor : ', arrayRandom, cc);

  } catch (error) {
    console.log('exampleAwaitPromiseAllFor error :',error);
  }
}
exampleAwaitPromiseAllFor()

PS: Ton api svelte doit sans doute empêcher les sollications trop fréquentes car il me fait souvent « Failed to generate random number. Please try again », et si j’essaie https://www.randomnumberapi.com il me fait des erreurs CORS, j’ai donc modifié légèrement l’appel de l’api. (Par contre ça ne fonctionne que sous firefox cette modif CORS, mais bon c’est un détail).

1 « J'aime »

Comme on l’a vu, les promesses fonctionnent dans Vue 3, et j’ai pu les mettre en œuvre après ces échanges, ceci dans mes données calculées mais après coup :slight_smile:

En revanche visiblement l’utilisation des promesses n’a pas l’air d’être possible dans Vue 3 en directe pour les données computed directement dans le template. Il faut utiliser ce contournement de lire la donnée dans le then du fetch(exemple en Vue 2 option api), ce que je fais avec pinia en Vue 3 composition api:

   MonStore.getMonApi(props.id).then((data) => {
    
        REACT.mavar= data.find(
          (todo) => {
            return todo.id == detail_computed.value.id;
          }
        );      
    });

Des commentaires ou points de vue :slight_smile: :stuck_out_tongue: ?

Mon cas d’utilisation est pour une donnée qui tarde à être disponible au démarrage : la donnée computed alimente bien un v-model mais je ne peux la réutiliser dans le setup() pour d’autre chose au démarrage: elle est vide (par contre l’autre,detail_computed, est bien disponible.

Édit:

Cette ligne en revanche fonctionne ci-dessous depuis longtemps. Peut-être que c’est grâce au paramètre props.id du composant Vue qui fait que cette donnée est filtrée au démarrage et que ça utilise la donnée state du Store et non la donnée calculée computed:

const detail_computed = computed(() => {
      return MonStore.dataStoreState.find((todo) => {
        return todo.id == props.id;
      });
    });

Mais n’empêche que m’a permit d’appréhender ces problématiques, et que visiblement on ne peut pas utiliser de données asynchrones avec les computed.

Édit 2:

Solution pas du tout asynchrone, ou en tout cas masquée par Vue:

    const madonnee = computed(() => {
      return MonStore.dataStoreState1.find((x) => {
        return (
          x.id1==(toRaw(MonStore2.dataStoreState2.find((y) => {
              return y.id2== props.id;
            })).id1)
        )
      })
    });

Les framework reactifs ne sont pas ma spécialité, mais je pense qu’à ta place je n’essaierai pas de mettre de l’asynchrone dans mes valeurs computed.

Je me trompe peut être mais selon moi, la philosophie des computed, c’est qu’elle ne dépendent que des infos qu’on a stocké dans nos variables classiques, on n’est pas sensé chercher dans les fonction computed des infos qu’on ne connait pas déjà.
Mettre de l’asynchrone dans une fonction computed, ça revient à dire qu’on a besoin chercher des infos ailleurs (vu que l’asynchrone est utilisé pour des appels reseau, base de donnée, librairie externe…), a priori ce sont des infos qu’on n’aura pas stocké dans nos variables.

A mon avis, sur cette problématique, au lieu d’utiliser un computed directement, il faut faire en sorte qu’au changement de ta donnée ID, tu lance une fonction qui va faire la recherche asynchrone, et au retour de la promesse changer une vrai variable de ton store ou de ton composant. C’est cette variable que tu dois utiliser dans ton computed au lieu d’y faire ton appel asynchrone.

1 « J'aime »

C’est quoi ta spécialité ? :slight_smile:

Oui c’est ce que j’ai fini par comprendre (et le créateur de Vue refuse les computed asynchrone pour cette raison).

Mon cas en cours, cf mon dernier message, est en fait un sous formulaire récupérant un identifiant permettant de récupérer une ligne d’une donnée amont computed1 d’un DataStore1, et affichant un <select> pointant sur computed2 d’un DataStore2.
* computed1 et computed2 sont bien remplis dans le template
* mais la jointure permettant de mettre à jour l’<option> de départ ne ramenait rien sauf à faire un nouveau computed à partir des DataStore d’origine, voir mon message précédent (ou un SetTimeOut() à l’origine) et pas à partir de computed1 et computed2. Mais ce n’est pas satisfaisant si je voulais le rendre évolutif à partir de données computed et non computed, et comprendre cela pour d’autres cas ultérieures.

Donc j’ai fait ça avec les watchers VueJs (que j’utilisais déjà par ailleurs, lien) après avoir lu ta réponse et un post Stackoverflow, mais sans promesse (snif), et avec la variable REACT=reactive() que j’utilise depuis mes débuts en Vuejs.

    let REACT = reactive({
      Data1List_charge: false,
      Data2List_charge: false,
      Data2List_options_id_courant: [
        {
         id2: 0,
        },
      ],
    });

    MonStore.getData1(props.id).then(() => {
      REACT.Data1List_charge= true
    });

    MonStore.getData2().then(() => {
      REACT.Data2List_charge= true
    });

    const computed1= computed(() => {
      return MonStore.DataStore1.find((todo) => {
        return todo.id1 == props.id_fenêtre_appelante;
      });
    });

    const computed2= computed(() => {
      return MonStore.DataStore2;
    });

    watchEffect(() => {
      if (REACT.Data1List_charge && REACT.Data2List_charge) {
        REACT.Data2List_options_id_courant= toRaw(computed2.value).find(
          (todo) => {
            return todo.id2== toRaw(computed1.value).id2;
          }
        );
      }
    })

PS: J’ai passé tout mon programme en promesses et tout fonctionne mieux et plus rapidement en plus, même les plus inattendus : (async function () { return await GetMonApi() })().then((a) => {$('#MaTable').DataTable({}) }).

CommitStrip sur le refactoring (22/06/2021) :slight_smile:

« Ça nous hante ! »