Utilizzo di call back al posto di yield

Nel caso si abbia la necessità nel richiamare una funzione asincrona senza usare la yield, perché voglio che il processo continui e non aspetti il risultato, devo utilizzare la call back.

Quindi se ho una funzione come la seguente definita a livello di applicazione:

App.Session.prototype.function1 = function(ms)
{
  var retVal = null;
  //
  app.sleep(ms, function (result, error) {
    if (result) {
      console.log("PG - true");
      retVal = true;
    } else {
      console.log("PG - false");
      retVal = false;
    }   
  });
  //
  return retVal;
};

In questa funzione ho tolto la yield al metodo app.sleep e aggiunto una call back per simulare una situazione reale che potrebbe capitare se occorre utilizzare un modulo Node.JS che non è già integrato su Instant Developer Cloud ed ha metodi asincroni che quindi prevedo l’utilizzo delle call back.

Questa funzione ritorna null e non è utilizzabile senza l’implementazione della call back nel nostro codice utilizzandola con:

let res = app.function1(2000);

Questa funzione quindi non risulta asincrona e l’ide non aggiunge e non permette di utilizzare la yield nel suo richiamo.
Come gestiamo questa casistica?

Possiamo utilizzare una proprietà di applicazione da impostare nella nostra funzione e aggiungere l’evento onChange alla proprietà che poi andrà a fare cose sulla nostra applicazione.

Quindi il codice potrebbe diventare:

App.Session.prototype.function1 = function(ms)
{
  var retVal = null;
  app.retValFunction1 = null;
  //
  app.sleep(ms, function (result, error) {
    if (result) {
      retVal = true;
    } else {
      retVal = false;
    }
    app.retValFunction1 = retVal;
    return;
  });
};

Questa versione la usiamo così:

app.function1(2000);

Poi avremo l’evento onChange della proprietà app.retValFunction1:

App.Session.prototype.retValFunction1_onChange = function(newValue)
{
  console.log("PG - newValue retVal ", newValue);
  //
  if (newValue !== null)
    App.PageController.postMessage(app, {retVal : newValue});
};

Il metodo postMessage della videata PageController (quella che eredita da Ionic.MainPage) manda un messaggio alla videata in primo piano e questa nell’evento onMessage può gestire il valore inviato.

2 Mi Piace

Interessante poter sfruttare gli eventi delle proprietà per sopperire ad un problema di asincronicità, mi sarebbe piaciuto averlo anche su Foundation.
Comunque col fatto dell’asincronicità su Cloud è necessario veramente pensar bene a come far funzionare le cose, è proprio una filosofia di pensiero completamente diversa.

2 Mi Piace

@d.termini è proprio come dici tu sono sue modi diversi di procedere nella programmazione. Poi il fatto che Cloud di default si comporta come se fosse sincrono con le yield semplifica le cose.

Mi è però capitato con Cloud di dover usare la call back e togliere la yield in una chiamata ad una Web API che mi restituiva una collection di prodotti che impiegava molto tempo ad essere eseguita (30 o 40 secondi) e quindi la relativa videata lasciava l’utente in attesa per troppo tempo.
Ho fatto in modo che il caricamento dei prodotti avvenisse ad avvio applicazione e nella call back andavo a caricare una proprietà dell’applicazione che conteneva la collection di prodotti.
Nell’evento onChange della proprietà che contiene la collection dei prodotti ho messo poi una postMessage per mandare alla videata relativa il messaggio di caricare i record nella DataMap dei prodotti.
La videata prodotti quando si apre verifica se la proprietà di applicazione che contiene la collection sia carica e nel caso contrario visualizza uno spinner che avvisa dell’attesa.

2 Mi Piace

Sto cercando di capire come risolvere un certo comportamento con le funzioni asincrone.
Nel mio caso sono funzioni scatenate direttamente dal framework (es onDataChange delle datamap).

Sono nella seguente situazione, ho 2 datamap che caricano delle risorse che mi servono per calcolare degli status da mostrare a video.
Entrambe le datamap hanno l’evento onDataChange che esegue un’operazione in cui sono necessari i valori di entrambe le datamap.
Di norma se arriva via sync la modifica ad una o all’altra è ininfluente e voglio che sia sempre eseguita la funzione.

In certe situazioni chiamo il reload delle datamap perché ho bisogno di ricaricare tutti i valori (es nell’evento onBack perché pare che ogni tanto si perda le notifiche di variazione documentale se sono su un’altra pagina e torno indietro)

yield $DM1.reload();
yield $DM2.reload();
// passa un po' di tempo
// scatta $DM2.onDataChange()
// scatta $DM1.onDataChange()
// o viceversa

Come potrei fare per capire che ENTRAMBE sono state caricate ed eseguire solo una volta la funzione al termine del caricamento?

Inizialmente avevo pensato a mettere una variabile di controllo che accendevo prima e spegnevo dopo il reload di entrambe ed eseguire io la funzione presente in onDataChange, ma ovviamente essendo asincrone la variabile di controllo veniva spenta prima che finisse effettivamente di ricaricarle.

Stavo quindi valutando di togliere lo yield ed usare una callback ma mi sono arenato perché il codice diventa enormemente complesso con vari livelli di annidamento e si perde la cognizione di che parti restano asincrone e quali invece diventano sincrone.

Su C# ricordo che ho modo di dare una sequenza di azioni asincrone da eseguire e dirgli di eseguire una callback solo quando tutte sono state eseguite, ma in Node.js non ho idea.

Tu come faresti @paolo.giannelli ?

1 Mi Piace

@d.termini si potrebbe risolvere come segue il tuo problema.

Occorre scrivere il caricamento delle due datamap mediante il metodo loadCollection in modo da poterne implementare la call back.

Diciamo che $DM1 carica documenti Order e $DM2 documenti Product possiamo caricarli mediante questo codice:

view.callCaricate = 0;
//
// Ordini
App.NWBE.Order.loadCollection(app, undefined, undefined, function (result, error) {
    if (!error) {
      view.collCaricate += 1;
      view.collOrdini = result;
    }
    else {
      view.collOrdini = null;
    }
  });
  // Prodotti
  App.NWBE.Product.loadCollection(app, undefined, undefined, function (result, error) {
    if (!error) {
      view.collCaricate += 1;
      view.collProdotti = result;
    }
    else {
      view.collProdotti = null;
    }
  });

Dove quelle che seguono sono proprietà della videata cone contiene le datamap:

  • view.collCaricate è una proprietà integer che vale 2 se tutte e due le collection sono state caricate
  • view collOrdini è una proprietà collection di Order
  • view collProdotti è una proprietà collection di Order

A questo punto aggiungiamo l’evento onChange alla proprietà view.collCaricate e in questo scriviamo:

App.Ordini.prototype.collCaricate_onChange = function(newValue)
{
   if (newValue === 2) {
     $DM1.collection = view.collOrdini;
     $DM2.collection = view.collProdotti  
  }
};

Cosa ne dici @d.termini può andare?

2 Mi Piace

Penso di sì, a meno che non si voglia implementare la stessa soluzione adottata da C# questa soluzione è abbastanza semplice da scrivere e manutenere.
Bisogna comunque ricordarsi di gestire il numero da verificare ogni volta che si va ad aggiungere o rimuovere una chiamata (difficile ma col tempo le esigenze possono cambiare).
Aggiungerei su

App.Ordini.prototype.collCaricate_onChange = function(newValue)
{
   if (newValue === 2) {
     $DM1.collection = view.collOrdini;
     $DM2.collection = view.collProdotti;
     // resetto la variabile
     collCaricate=0;
  }
};

Anche un reset del valore a 0 così non torna a chiamarla per sbaglio o non chiamarla proprio se diventa 3, 4 e così via.

1 Mi Piace

Stavo ripensando a questo sistema, bisogna stare attenti ad incrementare la variabile di esecuzione della callback solo in determinate condizioni.

Ad esempio nel mio caso la DM2 viene caricata anche più volte in base ad altri campi che applicano dei filtri, e non è detto che sia DM1+DM2, ma la funzione scatterebbe anche facendo DM2+DM2 perché la modifica di un campo ha scatenato una cascata di eventi che ha fatto in modo di chiamare 2 volte la load di DM2.

Un esempio: ho 2 filtri che influiscono su DM2 e a loro volta i filtri dipendono tra loro, mettiamo F1 = tipo sottoconto e F2 = codice sottoconto, su DM2 carico gli ordini.
Se cambio F1 molto probabilmente F2 non sarà più valido per il tipo che ho selezionato, quindi resetto F2.
Entrambi chiamano load di DM2 direttamente dall’evento onChange.
Edit: siccome può essere che voglio caricare tutti gli ordini per tipologia sottoconto lasciando vuoto il filtro sul codice, devo per forza gestire la load di DM2 anche sul tipo, altrimenti l’avrei gestita solo sul codice.

Probabilmente andrebbe rivista la gestione dei meccanismi che ho implementato perché a suo tempo ero ancora “newbie” di InDe Cloud quindi ho tentato di applicare logiche Foundation stile eventi di videata (es. onUpdatingRow) che già lì avevano effetti di loop o scatenati comunque più volte in base a come li scrivevi.

Dovrei quindi capire in che situazione mi trovo e non scatenare gli altri eventi di onChange dei campi dipendenti oppure scatenarli solo parzialmente perché comunque potrebbero dover fare qualcosa.

Ovviamente mettere variabili di controllo a pioggia e gestirle nel modo corretto rende il codice veramente complesso, oltretutto se ci aggiungiamo l’asincronicità si rischia veramente di non capirci più niente anche a debuggare!

1 Mi Piace

Su questo sono pienamente d’accordo! :smiley:

In un caso complesso come il tuo @d.termini costruito quando avevi poca esperienza di Instant Developer Cloud forse vale proprio la pena di ristrutturarlo da zero.

Potrebbe essere anche interessante creare un progetto che replica il caso complesso renderlo pubblico e vedere come ognuno di noi della Community lo risolverebbe!

1 Mi Piace

Ciao @paolo.giannelli , con un’assistenza per un problema sull’evento onChange delle proprietà DO sono venuto a conoscenza di una cosa interessante:

… ho parlato del caso con Matteo che mi dice che l’evento onChange delle proprietà del documento non dovrebbe essere implementabile, quindi inseriremo una segnalazione interna.
Dovresti, invece di implementare onChange() della proprietà, usare onPropertyChange() del documento scrivendo qualcosa tipo:

App.DO.Orderdetails.prototype.onPropertyChanged = function(propName, options)
{
  if (propName === "catId") {
    if (this.getOriginalValue(this.catId) !== this.catId)
      this.productid = undefined;
  }
};

Non so se intenda solo l’onChange delle proprietà DO oppure di qualsiasi proprietà, se fosse di “qualsiasi” allora renderebbe inutilizzabile il meccanismo descritto in questo topic.

In ogni caso se qualcuno (come me) volesse usare questa tecnica con la DO meglio se fa come consigliato da Matteo in modo da non trovarsi con del codice non funzionante nel caso venga modificato il comportamento.

1 Mi Piace