Aller au contenu
Fredmas

Questions de débutant en Quick Apps sur HC3

Recommended Posts

#5 Question 5 : appeler une fonction B et attendre son résultat pour l'utiliser au sein d'une fonction A avant de continuer la suite du code du QA

 

Dans une fonction A dans un QA j'appelle une autre fonction B, mais j'ai besoin d'attendre le résultat de cette autre fonction B avant de continuer à dérouler le reste de la fonction A.

Comment coder "proprement" pour que la ligne de 17 attende l'exécution et le résultat d'une fonction appelée à la ligne de code 16 ?

Dans mes premiers essais la ligne après l'appel de la fonction B se déroule avant d'avoir terminé la fonction B et de connaître son résultat dont j'ai besoin pour continuer :wacko:

 

Par exemple :

function QuickApp:onInit()
	self:debug("onInit")
	jsonTableCurrent = {}
end

function currentData()
	print("This function is checking regularly current meteo")
	local http = net.HTTPClient()
	--blabla
	jsonTableCurrent = json.decode(response.data)
end

function mainCode(self)
	self:debug("This function is checking regularly the meteo for prevision")
	currentData()
	self:debug(jsonTableCurrent.name)
end

Dans cet exemple, dans mainCode(self), la ligne "self:debug(jsonTableCurrent.name)" s'exécute avant d'avoir terminé d'exécuter la fonction "currentData()" de la ligne précédente.

 

Donc comment faire pour ne pas exécuter "self:debug(jsonTableCurrent.name)" tant que "currentData()" n'est pas terminé ? :blink:

J'ai bien pensé et essayé un setTimeout d'1s (et ça fonctionne très bien) :

function mainCode(self)
	self:debug("This function is checking regularly the meteo for prevision")
	currentData()
	fibaro.setTimeout(1000, function() self:debug(jsonTableCurrent.name) end)
end

mais je ne trouve pas cela propre. En plus si ma fonction"currentData()" devait exceptionnellement prendre plus de temps que d'habitude et dépasser ma valeur dans setTimeout, mon QA planterait.

Je préfèrerais une solution permettant simplement d'attendre réellement la fin de "currentData()" avant de passer à la ligne suivante :P

C'est peut-être tout bête, mais je n'ai pas trouvé après relu plusieurs doc :D

 

Partager ce message


Lien à poster
Partager sur d’autres sites

Bienvenue dans le monde merveilleux de l'asynchronisme :D

 

Il faut utiliser une fonction de rappel (callback) :

function QuickApp:onInit()
	self:debug("onInit")
end

local function currentData(callback)
	print("This function is checking regularly current meteo")
	local http = net.HTTPClient()
	http:request("http://...", {
		success = function()
			--blabla
			local jsonTableCurrent = json.decode(response.data)
			callback(true, jsonTableCurrent)
		end,
		error = function(msg)
			--blabla
			callback(false, msg)
		end,
	}
end

function mainCode(self)
	self:debug("This function is checking regularly the meteo for prevision")
	currentData(
		function(success, data)
			if success then
				self:debug(data.name)
			else
				self:error("ça s'est pas bien passé :", data)
			end
		end
	)
end

Tu constates qu'il y a une première fonction de rappel lors du retour de http:request..... celle-ci en appelant une seconde, la tienne.

C'est ultra puissant, mais pas évident à maitriser au début.

 

Maintenant tu peux aller approfondir avec le sujet suivant :

 

Et aussi celui-ci qui va prendre de l'importance à mesure que tu utilises des fonctions réputées pour planter de temps en temps (httprequest et json.decode) :

 

 

Remarque : définir http à chaque passage de boucle n'est pas judicieux.

Il vaut mieux le définir une seule fois à l'initialisation du QA, donc dans onInit, et l'utiliser par la suite.

A ce sujet, voir la page 2 de 1er tuto dont j'ai donné le lien ci-dessus, on a abordé cette problématique.

  • Like 1

Partager ce message


Lien à poster
Partager sur d’autres sites
il y a 35 minutes, Lazer a dit :

Bienvenue dans le monde merveilleux de l'asynchronisme :D

 

Il faut utiliser une fonction de rappel (callback) :

Purée ça fonctionne  :13: Merci ;)

Pas encore tout compris, mais ça fonctionne :D

 

 

il y a 35 minutes, Lazer a dit :

Tu constates qu'il y a une première fonction de rappel lors du retour de http:request..... celle-ci en appelant une seconde, la tienne.

C'est ultra puissant, mais pas évident à maitriser au début.

Oui, je viens de relire au moins 10 fois, et je continue :D

 

 

il y a 35 minutes, Lazer a dit :

Maintenant tu peux aller approfondir avec le sujet suivant :

 

Et aussi celui-ci qui va prendre de l'importance à mesure que tu utilises des fonctions réputées pour planter de temps en temps (httprequest et json.decode) :

 

OK merci beaucoup. Je vais prendre le temps de lire ces 2 sujets à tête reposée ce soir ;)

 

 

il y a 35 minutes, Lazer a dit :

Remarque : définir http à chaque passage de boucle n'est pas judicieux.

Il vaut mieux le définir une seule fois à l'initialisation du QA, donc dans onInit, et l'utiliser par la suite.

A ce sujet, voir la page 2 de 1er tuto dont j'ai donné le lien ci-dessus, on a abordé cette problématique.

Compris, je viens de regarder. Pour ne pas polluer ce topic et mélanger tous les sujets, dans ce cas si besoin je questionnerai directement sur l'autre sujet :P

 

Partager ce message


Lien à poster
Partager sur d’autres sites

En fait le principe de base est relativement simple, puisque ça consiste à appeler une fonction en lui donnant un paramètre, ce paramètre est une variable de type "function" (pour rappel en LUA, les fonctions sont des variables au même titre que les autres type : string, table, number, etc)

 

La fonction appelée s'exécute... puis à un moment elle va exécuter la fonction qui lui a été donnée en paramètre (le paramètre que j'ai appelé "callback" dans mon exemple)

 

Une fois que tu as compris ça, il n'y a plus de limite, tu peux enchainer les rappels de fonctions à volonté.

Partager ce message


Lien à poster
Partager sur d’autres sites

Ca y est ! A force de relire et faire des exercices cérébraux pour comprendre la dynamique et ton message, je viens d'être éclairé, ou plutôt Laserifié :2:

 

Maintenant que j'ai compris sa lecture, on verra si j'arrive à le réutiliser dans une autre situation ou un autre QA en écriture seul comme un grand :P

Partager ce message


Lien à poster
Partager sur d’autres sites

@Lazer pour continuer de creuser/philosopher un peu plus les appels de fonction, avec 2 appels http et donc 2 callback, que penses-tu du code ci-dessous (en retirant la plupart des commentaires pour plus de lisibilité:

 

function currentData(self, callback1, callback2)
	self.http:request("blabla1" {
		success = function(response)
			if response.status == 200
			then
				self:debug("Success: status = "..tostring(response.status))
				local jsonTable = json.decode(response.data)
				callback1(true, jsonTable)
			else
				self:warning("Error: status = " ..tostring(response.status))
			end
		end,
		error = function(err)
			callback1(false, err)
			self:warning("Error: " ..err)
		end
	})
	self.http:request("blabla2" {
		success = function(response)
			if response.status == 200
			then
				self:debug("Success: status = "..tostring(response.status))
				local jsonTable = json.decode(response.data)
				callback2(true, jsonTable)
			else
				self:warning("Error: status = " ..tostring(response.status))
			end
		end,
		error = function(err)
			callback2(false, err)
			self:warning("Error: " ..err)
		end
	})
end

function mainCode(self)
	currentData(self,
		function(success1, datajsonTable1)
			if success1
			then self:debug(datajsonTable1.name1)
			else self:error("ça ne s'est pas bien passé :", datajsonTable1)
			end
		end,
		function(success2, datajsonTable2)
			if success2
			then self:debug(datajsonTable2.name2)
			else self:error("ça ne s'est pas bien passé :", datajsonTable2)
			end
		end
	)
end

 

Modifié par Fredmas

Partager ce message


Lien à poster
Partager sur d’autres sites
function currentData(self, callback)
  self.http:request("http://worldtimeapi.org/api/timezone/Europe/Stockholm", {
      success = function(response)
        if response.status == 200
        then
          self:debug("Success: status = "..tostring(response.status))
          local jsonTable = json.decode(response.data)
          callback(true, jsonTable)
        else
          self:warning("Error: status = " ..tostring(response.status))
        end
      end,
      error = function(err)
        callback(false, err)
        self:warning("Error: " ..err)
      end
    })
end

function mainCode(self)
  currentData(self,
    function(success1, datajsonTable1)
      if success1
      then self:debug("1",datajsonTable1.datetime)
        currentData(self,function(success2, datajsonTable2)
            if success2
            then self:debug("2",datajsonTable2.datetime)
            else self:error("ça ne s'est pas bien passé :", datajsonTable2)
            end
          end)
      else 
        self:error("ça ne s'est pas bien passé :", datajsonTable1)
      end
    end
  )
end

function QuickApp:onInit()
  self.http = net.HTTPClient()
  mainCode(self)
end

 

Partager ce message


Lien à poster
Partager sur d’autres sites

...and you see that currentData(...) becomes a generic http get function...

function httpGet(self, url, callback)
  self.http:request(url, {
      success = function(response)
        if response.status == 200
        then
          self:debug("Success: status = "..tostring(response.status))
          local jsonTable = json.decode(response.data)
          callback(true, jsonTable)
        else
          self:warning("Error: status = " ..tostring(response.status))
        end
      end,
      error = function(err)
        callback(false, err)
        self:warning("Error: " ..err)
      end
    })
end

function mainCode(self)
  httpGet(self,"http://worldtimeapi.org/api/timezone/Europe/Stockholm",
    function(success1, datajsonTable1)
      if success1
      then self:debug("1",datajsonTable1.datetime)
        httpGet(self,"http://worldtimeapi.org/api/timezone/America/New_York",
          function(success2, datajsonTable2)
            if success2
            then self:debug("2",datajsonTable2.datetime)
            else self:error("ça ne s'est pas bien passé :", datajsonTable2)
            end
          end)
      else 
        self:error("ça ne s'est pas bien passé :", datajsonTable1)
      end
    end
  )
end

function QuickApp:onInit()
  self.http = net.HTTPClient()
  mainCode(self)
end

 

  • Like 1

Partager ce message


Lien à poster
Partager sur d’autres sites

Good, thank you :D

In your 2 examples, as the 2 functions are not called in parallel but one after the other, do I need to keep success1/datajsonTable1 and success2/datajsonTable2, etc., or can I rename all of them to simply success/datajsonTable

Partager ce message


Lien à poster
Partager sur d’autres sites

Ahh, you want to run them in parallell?

Partager ce message


Lien à poster
Partager sur d’autres sites

No absolutely not, I prefer one after the other like in your proposal.

 

My question was just about name of success1, success2, etc.

Partager ce message


Lien à poster
Partager sur d’autres sites

I think it's best to serialize http requests, as @jang suggested.

 

You can use the same name for success() arguments, the LUA compiler will use the last one.

But it can be dangerous because it can lead to confusion. A best practice is to use different names.

Partager ce message


Lien à poster
Partager sur d’autres sites
il y a 5 minutes, Lazer a dit :

I think it's best to serialize http requests, as @jang suggested.

Yes for sure, it was my purpose :P and for this reason we are talking about callback since yesterday ;)

 

 

il y a 5 minutes, Lazer a dit :

You can use the same name for success() arguments, the LUA compiler will use the last one.

But it can be dangerous because it can lead to confusion. A best practice is to use different names.

Clear, thank you. This was my question, and I understand your answer: yes I can (technically speaking), but I will not  :D

 

 

Partager ce message


Lien à poster
Partager sur d’autres sites
Il y a 11 heures, Fredmas a dit :

Yes for sure, it was my purpose : Pand for this reason we are talking about callback since yesterday;)

 

 

Clear, thank you. This was my question, and I understand your answer: yes I can (technically speaking), but I will not  : D

 

 

 

Well, the local variables just shadow each other - it will work with same name - I choose different ones just for clarity. ...and clarity is good!

 

Here you have 3 versions.

  1. Parallell - all request run and handled in parallell.
  2. Sequential - one request after another, handled in sequence. 
  3. Parallell_join - The request run in parallell and the response collected in a common result that is sent to the callback when all results are ready. The advantage of sequential with the performance of parallell :-) 
function httpGet(self, url, callback)
  self.http:request(url, {
      success = function(response)
        if response.status < 400 then
          callback(true, url, response.data)
        else
          callback(false, url, response.status)
        end
      end,
      error = function(err)
        callback(false, url, err)
      end
    })
end

local urls2call = {
  "https://www.apple.com",
  "https://www.google.com",
  "https://www.amazon.com",
  "https://www.facebook.com",
}

function parallell(self, urls ,handler)         -- call all urls in parallell, handle asynchronous
  for _,url in ipairs(urls) do 
    httpGet(self, url, handler)
  end
end

function sequential(self, urls, handler)          -- call all urls in sequence
  local i = 1
  local function callback(success, url, result)
    handler(success, url, result)
    i=i+1
    if i  <= #urls then
      httpGet(self,urls[i], callback)
    end
  end
  httpGet(self, urls[i], callback)             
end

function parallell_join(self, urls, handler)        -- call all urls in parallel, call join
  local  result = {n=0,responses={}}
  for _,url in ipairs(urls) do 
    httpGet(self, url,
      function(success, url, response)
        result.responses[#result.responses+1]={url=url,success=success,response=response}
        result.n = result.n+1
        if result.n == #urls then handler(true, nil, result.responses) end
      end)
  end
end


function QuickApp:onInit()
  self.http = net.HTTPClient()

--  parallell(self,urls2call,
--    function(success, url, result) self:debug("Success "..tostring(success).." "..url) end
--  )
--  sequential(self,urls2call,
--    function(success, url, result) self:debug("Success "..tostring(success).." "..url) end
--  )
  parallell_join(self,urls2call,
    function(success, url, result)
      for _,r in ipairs(result) do
        self:debug("Success "..tostring(r.success).." "..r.url) 
      end
    end
  )
end

 

Modifié par jang
  • Like 1

Partager ce message


Lien à poster
Partager sur d’autres sites

Guys I like you :P

More we talk, more you have ideas to share and to propose :D

As @Lazer said several times in the past, building codes the main limit is knowledge and creativity :unsure:

Partager ce message


Lien à poster
Partager sur d’autres sites

×