NEWS
[Neu] Diverse async-Funktionen im JavaScript-Adapter
-
Hi,
muss mal die Werbetrommel rühren
@AlCalzone hat mit Version 4.8.0 (im Latest derzeit 4.8.4) des JavaScript-Adapters async-Funktionen implementiert, das ist wirklich mega
(siehe auch Github issue #635)Hier mal ein schnelles Beispiel, es werden nacheinander in einer Schleife 10 States erstellt und dessen Werte dann direkt danach ausgelesen:
test(); async function test() { try { for (let i = 1; i < 11; i++) { const id = `0_userdata.0.async-test.state_${i}`; if (await existsStateAsync(id)) { log(`State ${id} already exists`, 'warn'); } else { await createStateAsync(id, {type:'number', read:true, write:true, def:i }); const stateObject = await getStateAsync(id); if (stateObject && stateObject.val) { log(`State '${id}' created, value: '${stateObject.val}'`) } else { log(`Unable to get state value of '${id}'.`, 'error'); } } } } catch (error) { log(`Unexpected error - ${error}`, 'error'); } }
Lektüre (Beispiel, ansonsten fragt Google): https://javascript.info/async-await
async/await macht es wirklich sehr einfach, mit Promises zu arbeiten, eigentlich muss man sich dadurch so gut wie nicht mehr damit beschäftigen
es vermeidet auch die berüchtigte Callback Hell. async/await und in Verbindung mit try/catch als Error Handling erleichtert euch da echt vieles.Ich kann nur empfehlen, dies in euren Scripts zu nutzen, und wenn man später mal selbst einen Adapter entwickeln möchte, ist euer Wissen, dass ihr euch dadurch aneignet, Gold wert.
-
@Mic @AlCalzone Schöne Sache Was mir auffällt, wenn ich i mit 0 starte, erhalte ich immer einen Fehler für den ersten State(der State wird aber richtig erstellt). Starte ich mit einem beliebigen Startwert außer 0, dann erhalte ich nie einen Fehler(nie/immer = ca. je 20 Läufe auf zwei Systemen)
18:26:11.848 info javascript.1 (646) script.js.Aktiv.ScriptJS: registered 0 subscriptions and 0 schedules 18:26:11.995 error javascript.1 (646) script.js.Aktiv.ScriptJS: Unable to get state value of '0_userdata.0.async-test.state_0'. 18:26:12.092 info javascript.1 (646) script.js.Aktiv.ScriptJS: State '0_userdata.0.async-test.state_1' created, value: '1' 18:26:12.189 info javascript.1 (646) script.js.Aktiv.ScriptJS: State '0_userdata.0.async-test.state_2' created, value: '2' 18:26:12.291 info javascript.1 (646) script.js.Aktiv.ScriptJS: State '0_userdata.0.async-test.state_3' created, value: '3' 18:26:12.387 info javascript.1 (646) script.js.Aktiv.ScriptJS: State '0_userdata.0.async-test.state_4' created, value: '4'
-
Ist ein Fehler in meinem Beispiel, weil ich die Existenz des State-Wertes mit "!" abfrage, also per falsy, was bei 0 als Statewert
false
ergibt.
Ich bessere oben auf die Schnelle nach und starte die Schleife ab 1 Genau so kann man natürlich auch ab 0 starten und anders im if/else darauf abfragen... -
@Mic
Worin unterscheidet sich die Handhabung von getObjectAsync(id) von getObject(id) ? -
@paul53 sagte in [Neu] Diverse async-Funktionen im JavaScript-Adapter:
@Mic
Worin unterscheidet sich die Handhabung von getObjectAsync(id) von getObject(id) ?Hi Paul,
bei
const obj = getObject(id);
können wir nicht sicher sein, dassobj
in der nächsten Zeile schon gesetzt ist, das wissen wir nur sicher dann im callback von getObject(). Machen wir einige getObject() in einer Schleife z.B., kann das in einer Callback-Hölle enden.Mit
getObjectAsync(id)
viel schöner umzusetzen:const obj = await getObjectAsync(id); if(!obj) throw(`Hier Fehlertext`); // Hier gehts weiter und wir machen was mit 'obj'....
-
@paul53
getObjectAsync
(und die anderen neuen Funktionen) kapseln den Callback weg. Stattdessen wird der Rückgabewert als Ergebnis vonawait
erhalten und Fehler können klassisch mit try-catch abgefangen werden.alt:
getObject(id, (err, obj) => { // ... mit obj arbeiten oder err behandeln }); // wird sofort ausgeführt
neu:
try { const obj = await getObjectAsync(id); // hier mit obj arbeiten } catch (err) { // hier Fehler behandeln }
Außerdem ist das Verhalten von getStateAsync etc. unabhängig davon, ob der Haken "alle States abonnieren" in den Adapter-Einstellungen gesetzt ist. Es gibt nie einen Callback und es ist immer egal, ob die Werte im Hintergrund asynchron geladen werden oder nicht.
-
@Mic sagte in [Neu] Diverse async-Funktionen im JavaScript-Adapter:
Ist ein Fehler in meinem Beispiel, weil ich die Existenz des State-Wertes mit "!" abfrage, also per falsy, was bei 0 als Statewert
false
ergibt.
Ich bessere oben auf die Schnelle nach und starte die Schleife ab 1 Genau so kann man natürlich auch ab 0 starten und anders im if/else darauf abfragen...Danke, Anfängerfehler meinerseits, das nicht gesehen zu haben. Andererseits hatte ich es dann doch noch selbst rausgefunden
-
@Mic sagte:
bei const obj = getObject(id); können wir nicht sicher sein, dass obj in der nächsten Zeile schon gesetzt ist
getObject(id) arbeitet synchron, indem es auf den Puffer der Javascript-Instanz zugreift. Diese Version funktioniert natürlich nicht mit Haken bei "Nicht alle Zustände beim Start abonnieren".
-
-
@paul53 sagte in [Neu] Diverse async-Funktionen im JavaScript-Adapter:
getObject
Bedeutet das, dass der js adapter zwangsläufig neu gestartet werden muß, wenn sich an den Aufzählungen etwas ändert?
-
Hallo,
die neuen Möglichkeiten durchdie async-Funktionen hören sich toll an.Nun versuche ich gerade meine alten Callback-Höllen aufzuräumen
Gibt es einen asynchronen Ersatz für
exec
oder muss man das mit Promises bauen?function fetchCropPictureMbCam () { return new Promise((resolve) => { cmd = `${wgetCmd} ${picturePathMbCam} ${mailboxCamURL}`; exec(cmd, async function (error, stdout, stderr) { log(`Exec: ${cmd}`); log(`Exec: stdout: ${stdout}, stderr: ${stderr}, error: ${error}`); cmd = `${imageMagickCmd} ${picturePathMbCam} ${imageMagickParams} ${picturePathMbCamCropped}`; exec(cmd, async function (error, stdout, stderr) { log(`Exec: ${cmd}`); log(`Exec: stdout: ${stdout}, stderr: ${stderr}, error: ${error}`); resolve(); }); }); }); } subscribe({id: dpMailboxFlap, change: "ne", val: 'open'}, async function(obj) { log('Mailbox: Flap has been opened'); setState(dpMailboxFillState, eMailboxFillState.filled, true); /* wait 5 seconds so that letters can be thrown in */ await wait(5000); await fetchCropPictureMbCam(); sendTo('pushover.0', 'send', { title: 'Briefkasten', message: "Es wurde Post eingeworfen.", file: picturePathMbCamCropped, }); });
EDIT:
ist das so richtig? dasresolve()
kommt an die Stelle wenn die Callbacks fertig sind? -
@stan23 Für exec gibts tatsächlich keinen Ersatz, weil das Rückgabeobjekt nicht ganz eindeutig wäre.
Dein Code ist so fast korrekt:async
vor denfunction
s infetchCropPictureMbCam
ist unnötig, da du darin keinawait
verwendestcmd
wird nirgends deklariert.
-
@AlCalzone said in [Neu] Diverse async-Funktionen im JavaScript-Adapter:
Dein Code ist so fast korrekt:
async
vor denfunction
s infetchCropPictureMbCam
ist unnötig, da du darin keinawait
verwendestcmd
wird nirgends deklariert.
Stimmt, das Erste ist ein Relikt aus meinen Versuchen, das Zweite war noch global deklariert.
Habe ich beides geändert und funktioniert.
Vielen Dank!Gibt es denn beim
setState
auch etwas zu beachten?
Ich hatte mir vor langer Zeit mal gemerkt dass man für HomeMatic die Einschaltdauer und den tatsächlichen Schaltbefehl so setzen soll:setState("hm-rpc.2.NEQ0115240.1.ON_TIME", 2*3600, function() { setState("hm-rpc.2.NEQ0115240.1.STATE", true); });
Ist das noch okay oder gibt es da auch eine komfortablere Variante? Oder einfach 2
setState
hintereinander weil die sich nicht überholen können? -
@stan23 sagte in [Neu] Diverse async-Funktionen im JavaScript-Adapter:
Oder einfach 2 setState hintereinander weil die sich nicht überholen können?
Eine "komfortablere" Variante für zwei getrennte States gibt es nicht. Mit
await setStateAsync(...)
kannst du sie aber einfach unterenander hängen, weil die sich nicht überholen (im Gegensatz zusetState
, wo du einen callback brauchst) -
@AlCalzone das finde ich komfortabel genug
-
@AlCalzone
Gibt es auch eine Lösung für einen async HTTP-Request? Oder soll man es am besten noch ähnlich wie das stan23 fürexec
gemacht hat lösen?Ich vermute mal, dass die
request
-Funktion von ioBroker auf das Node-Modul "request" aufbaut? Stimmt das? Das ist scheinbar auch seit Februar "deprecated": https://www.npmjs.com/package/request -
-
@noox sagte in [Neu] Diverse async-Funktionen im JavaScript-Adapter:
Gibt es auch eine Lösung für einen async HTTP-Request?
Entweder so wie mein Vorposter.
Alternativ ginge z.b. die u.a. von mir bevorzugte Library https://github.com/axios/axios - die Beispiele dort sind mit Promises und .then geschrieben, lassen sich aber selbstverständlich auch mitawait
nutzen -
@Nahasapee, @AlCalzone Vielen Dank!
Mit Axios geht das echt easy.Erster Versuch für simples Get mit JSON-Response in Typescript:
const axios = require('axios').default; class ... { public static async httpGetRequestJson(url: string, throwException: boolean = false): Promise<any> { let response: any; try { response = await axios.get(url, { timeout: 10000, }); } catch(exception) { log(`error fetching url ${url}: ${JSON.stringify(exception)}`, 'error'); if(throwException) throw exception; return undefined; } if(response.status != 200) { log(`error fetching url ${url}. Status: ${response.status} ${response.statusText}. Response: ${JSON.stringify(response)}`, 'error'); if(throwException) throw new Error(`Response status: ${response.status} ${response.statusText}.`); return undefined; } // log(response.data); return response.data; } }
Hier wurde das Thema eh auch schon aufgegriffen - hatte es aber erst jetzt (wegen dem Hinweis auf Axios) gefunden: https://forum.iobroker.net/topic/36039/alternative-für-request-paket
PS: Wer sich den response komplett ansehen möchte: Bei wir waren da zirkuläre Referencen drinnen, sodass JSON.stringify nicht funktionierte. Auf Stackoverflow gibt's ein paar Diskussionen dazu. Ein Workaround z.B. hier: https://stackoverflow.com/a/48845206
-
@AlCalzone sagte in [Neu] Diverse async-Funktionen im JavaScript-Adapter:
@noox sagte in [Neu] Diverse async-Funktionen im JavaScript-Adapter:
Gibt es auch eine Lösung für einen async HTTP-Request?
Entweder so wie mein Vorposter.
Alternativ ginge z.b. die u.a. von mir bevorzugte Library https://github.com/axios/axios - die Beispiele dort sind mit Promises und .then geschrieben, lassen sich aber selbstverständlich auch mitawait
nutzenIch stell mich leider mal wieder zu doof an, ich hab das Beispiel von Axios versucht umzusetzen, aber da kommt immer nurn leeres Objekt. Hat da evtl jemand nen Beispiel was läuft? Oder kann mir sagen was da verkehrt ist (ich und Objekte werden einfach keine Freunde mehr)?
const axios = require('axios'); getData(); async function getData() { try { const response = await axios.get('https://miot-spec.org/miot-spec-v2/instance?type=urn:miot-spec-v2:device:air-purifier:0000A007:zhimi-ma4:1',{timeout : 10000}); console.log("resp:"+JSON.stringify( response)); } catch (error) { console.error(error); } }