NEWS
Mein Javascript global und neue Art js-scripte zu schreiben
-
Liebe Forumsmitglieder!
Anhand der Fragen hier im Forum sehe ich dass viele die gleiche 'Lernphase' durchmachen wenn sie mit ioBroker beginnen.
Ich kam von Node-Red und hatte das verwendet um einige Dinge am Raspi zu steuern. Ohne gute Oberfläche und Management kann man aber nicht weit kommen und deshalb habe ich über einen ioBroker-Adapter zu ioBroker selbst gefunden.
In den ersten Wochen hab ich mich mittels diesem Forum und den Bordmitteln (Beispielscrips u.s.w.) in ioBroker eingearbeitet und einige scripte erfolgreich von Node-Red auf ioBroker übertragen.
Allerdings hab ich vermiss dass z.B. asynchrone Ereignisse in Sequenz nacheinander abgehandelt werden können ohne auf callback in callback in callback u.s.w. zurückgreifen zu müssen.
Da ich für meine früheren scripts für web und Node-Red schon etliche ES2015/ES6 Javascript-Fähigkeiten verwendet habe hatte ich diese in ioBroker vermisst.
Deshalb hab ich mir ein globales script gebastelt welches meine faule Art zu Programmieren etwas besser unterstützt.
Es verwendet einige neue Ansätze von ES2015 die teilweise schon in node 0.12 oder alle ab node 4.3 unterstützt werden.
Das sind:
-
Template Strings https://developer.mozilla.org/de/docs/W … te_strings
-
Arrow Functions https://developer.mozilla.org/en-US/doc … _functions
-
const und let Variablen https://developer.mozilla.org/en-US/doc … ents/const, https://developer.mozilla.org/en-US/doc … ements/let
-
und fast als wichtigstes: Promise's https://developer.mozilla.org/en-US/doc … ts/Promise
Mit den Template Strings kann man (auch mehrzeilige) strings mit integrierten kalkulierten Ergebnissen generieren die ideal für die Ausgabe und logs sind.
Die Arrow functions erlauben anstatt
> function(arg) { return arg+1; }
mit neuer Syntax> arg => arg+1
zu schreiben.const sind definitionen von variablen die konstant bleiben, und let definiert eine variable die nur in diesem Block gültig ist. Leider funktionieren let in ioBroker script nur bedingt da man nicht das ganze script auf "use strict"; schalten kann da einige tricks die ioBroker macht um die scripts 'abzuschirmen' dies verhindern. Man kann jedoch einzelne Funktionen mit "use strict"; abschotten und dann dort let verwenden.
Wer verstehen will welche ES2015/ES6 Fähigkeiten von welchen nodejs-Versionen unterstütz werden kann auf http://node.green/ nachschauen. Da ich nicht voraussetzte dass jeder node 6.9.x (die letzte LTS-Version) einsetzt verwende ich auch keine Teile die nur dort funktionieren.
So, nun liste ich zwei scripts, das erste ist mein globales script welches vor jedes andere script angehängt wird und die Definitionen enthält welche ich überall verwende.
Ich habe es auf genau 100 Zeilen getrimmt womit ich bei einem Fehler der dann z.B. in Zeile 222 angezeigt wird leicht ausrechnen kann dass der Fehler in Zeile 122 von meinem script ist (100 Zeilen vom global script und 122 vom auszuführendem script ergeben 222).
// globale Funktionen FJ const util = require("util"), // util.inspect http = require("http"), // http.get https = require('https'); // https.get var debuglog = 'debug'; // default level von _D(), kann im script dann z.B. auf 'info' geschalten werden um _D() auch im Info-Level anzuzeigen function _O(obj,level) { return util.inspect(obj, false, level || 2, false).replace(/\n/g,' ');} // print object as string like node would do on console but remove new lines function _J(str) { try { return JSON.parse(str); } catch (e) { return {'error':'JSON Parse Error of:'+str}}} // convert a JSON string securely to an object function _D(str,val) { log(`debug: ${str}`,debuglog); return val !== undefined ? val : str; } // Write debug message in log, optionally return 2nd argument function _W(str,val) { log(`warn: ${str}`,'warn'); return val !== undefined ? val : str; } // Write warning message in log, optionally return 2nd argument function _I(str,ret) { log(str); return ret !== undefined ? ret : str; } // Write normal information to log, optionally return 2nd argument function _N(fun) { return setTimeout.apply(null,[fun,0].concat(Array.prototype.slice.call(arguments, 1))); } // execute fun after pending IO in next schedule keeping arguments function idn(id) { // return object name name if it exist, otherwise return id. const obj = getObject(id); return ((obj && obj.common && obj.common.name) ? obj.common.name : id) + " "; } function pSeries(obj,promfn,delay) { "use strict"; // makes an call for each item 'of' obj to promfun(item) which need to return a promise. All Promises are executed therefore in sequence one after the other let p = Promise.resolve(); const nv = [], f = (k) => p = p.then(() => promfn(k).then(res => wait(delay || 0,nv.push(res)))); for(let item of obj) f(item); return p.then(() => nv); } function pSeriesIn(obj,promfn,delay) { "use strict"; // makes an call for each item 'in' obj to promfun(key,obj) which need to return a promise. All Promises are executed therefore in sequence one after the other let p = Promise.resolve(); const nv = [], f = k => p = p.then(() => promfn(k,obj).then(res => wait(delay || 0, nv.push(res)))); for(let item in obj) f(item); return p.then(() => nv); } function c2pP(f) { "use strict"; // turns a callback(err,data) function into a promise return function () { const args = Array.prototype.slice.call(arguments); return new Promise((res, rej) => { args.push((err, result) => (err && _N(rej,err)) || _N(res,result)); f.apply(this, args); }); }; } function c1pP(f) { "use strict"; // turns a callback(data) function into a promise return function () { const args = Array.prototype.slice.call(arguments); return new Promise((res, rej) => { args.push((result) => _N(res,result)); f.apply(this, args); }); }; } // these functions retry a promise max nretry times or repeat a promise nrepeat times with calling fn(arg) as the function which needs to return a promise function pRetry(nretry, fn, arg) { return fn(arg).catch(err => { if (nretry <= 0) throw err; return pRetry(nretry - 1, fn,arg); }); } function pRepeat(nrepeat, fn, arg, r) { r = r || []; return fn(arg).then(x => (nrepeat <= 0 ? Promise.resolve(r) : pRepeat(nrepeat - 1, fn,arg, r, r.push(x)))); } function wait(time,arg) { return new Promise(res => setTimeout(res,parseInt(time), arg))} // wait time (in ms) and then resolve promise with arg, returns a promise thich means the '.then()' will be executed after the delay function pGet(url,retry) { "use strict"; // get a web page either with http or https and return a promise for the data, could be done also with request but request is now an external package and http/https are part of nodejs. const fun = url.startsWith('https') ? https.get : http.get; return (new Promise((resolve,reject)=> { fun(url, (res) => { const statusCode = res.statusCode; const contentType = res.headers['content-type']; if (statusCode !== 200) { const error = new Error(`Request Failed. Status Code: ${statusCode}`); res.resume(); // consume response data to free up memory return reject(error); } res.setEncoding('utf8'); let rawData = ''; res.on('data', (chunk) => rawData += chunk); res.on('end', () => _N(resolve,rawData)); }).on('error', (e) => _N(reject,e)); })).catch(err => { if (!retry) throw err; return wait(100,retry -1).then(a => pGet(url,a)); }); } const pExec = c2pP(exec), // pExec is a Promise-version of exec(command,callback) pCst = c1pP(createState), // pCst is a Promise-Version of createState(id,...,callback) pSst = c2pP(setState); // pSst is a Promise-Version of setState(id,...,callback) function printSubs(scriptname) { "use strict"; // Print subscriptions const subscriptions = getSubscriptions(); let subscriptionCount = 0; if (scriptname === undefined) scriptname = 'alle'; for (let dp in subscriptions) { for (let s of subscriptions[dp]) if ((scriptname == 'alle') || (scriptname == s.name) || dp.match(scriptname) || s.name.match(scriptname)) _D(`on(${s.pattern.id}), im Script: ${s.name}, change: ${s.pattern.change}, logic: ${s.pattern.logic}`,subscriptionCount++); } _D(subscriptionCount < 1 ? "keine Subscrition enthalten. Filter: (" + scriptname +")" : 'Anzahl Subscriptions: ' + subscriptionCount + ' in javascript.' + instance + ', Filter: ("' + scriptname + '")'); return subscriptionCount; } /// End at line x00!!!
Anbei noch ein demo/Testscript welches die Anwendung verschaulichen soll. Einige Zeilen sind auskommentiert aber Ich werde weitere Beschreibungen posten die dann die unterschiedlichen Funktionen erläutern und die Beispiele zeigen.
// AlarmSystem var stop = false; // Wird von onStop verwendet und kann anderen Hintergrundprprogrammen im script zeigen dass das script herunterfährt und nichts neues mehr gestartet werden soll const dpPfad = "DemoTest.", // In welchem Pfad sollen die Datenpunkte angelegt werden. String mit "." am Ende. instanz = "javascript." + instance + ".", alarm = dpPfad + "Alarm", alarmPot = dpPfad + "AlarmPotential", alarmState = dpPfad + "AlarmState", // dns = require('dns'), idwhoHere = "radar.0.whoHere"/*whoHere*/; const led = "rpi.0.gpio.26.state"/*GPIO 26 is a led on my Raspberry Pi using rpi2 adapter*/, pir = "rpi.0.gpio.19.state"/*GPIO 19 is a PIR sensor on my Raspberry */; onStop(function(callback){ try { stop = true; _W("onStop called. Shutting down."); // do whatever cleanup! } catch(e) { _W(`Err in stop ${_O(e)}`); } finally { callback && callback(); } },100); function createStates () { // Die Funktion returniert eine Promise damit sie verschachtelt werden kann! _D("Create States"); const states = [ // Ich kreiere eine objekt-Array mit allen zu erzeugenden Objekten { id: alarm, val: false, common: {type: 'boolean',name:alarm, unit: '',role: 'state',write:true}}, { id: alarmPot, val: "", common: {type: 'string',name:alarmPot, unit: '',role: 'state',write:true}}, { id: alarmState, val: 0, common: {type: 'number',name:alarmState, unit: '',role: 'state',write:true,max:2,min:0,states:'0:Aus;1:Nacht;2:Ein'}}, ]; return pSeries(states, (item) => pCst(/* _D(`Created ${item.id}=${_O(item.val)} with ${_O(item.common)}`,item) */ item.id,item.val,item.common,{}), 100) .then(x => wait(100,_D('end created states:'+_O(x)))); } function onAnlegen() { // Die Funktion returniert eine Promise damit sie verschachtelt werden kann! _D("on anlegen"); on(pir, led); // GPIO 19 is a PIR sensor on my Raspberry on({id: instanz+alarmPot , change:'all'}, obj => obj && obj.state && _D(`${obj.state.from} meldet potenziellen Alarm mit ${obj.state.val}`)); on({id: instanz+alarmState , change:'all'}, obj => obj && obj.state && _D(`${obj.state.from} schaltet AlarmState auf ${obj.state.val}`)); on({id: idwhoHere , change:'ne'}, obj => _D(`Avalability changed to ${obj && obj.state && obj.state.val}`)); return wait(_D('end on anlegen',100)); } function main() { _D(`start main`); return pSst(instanz+alarmPot, "alarmTest") .then(() => pSst(instanz+alarmState, 0)) /* .then(()=> pSeries([11,22,33], k => Promise.resolve(_D('kk='+k)),300)) .then(x=> _D('k='+x)) .then(()=> pSeriesIn([1,2,3], (k,o) => Promise.resolve(_D(`k=${k} i=${o[k]}`)),10)) .then(x=> _D('k='+x)) .then(()=> pExec('whoami')) .then(iAm => logs(`I am ${iAm}`)) .then(x=> _D('kk='+x)) .then(()=> pSeries([111,222,333], k => Promise.resolve(_D('kkk='+k)),1000)) .then(x=> _D('kkk='+x)) .then(() => pRepeat(10,(arg) => pSst(led, false, false) .then(()=> wait(arg)) .then(()=> pSst(led,true,false)) .then(()=> wait(arg)) ,200)) .then(() => pExec('sudo arp-scan -lgq --retry=10')) .then(res => res && res.match(/(\d*\.){3}\d*\s*([\dA-F]{2}\:){5}[\dA-F]{2}/gi)) .then(res => pSeries(res, item => { const s = item.split('\t'); return pGet('http://api.macvendors.com/'+s[1]) .then(x => x.trim(), err => 'Vendor not found') .then(x => s.push(x) && c2pP(dns.reverse)(s[0])) .then(nam => s.concat(nam), err => s.concat(['N/A'])) .then(res => res.join('; ')); },10)) .then(res => pSeries(res,item => Promise.resolve(_D(_O(item))))) */ .then(()=> wait(_D('end main',100))); } // Handle initialization and test calls wait(100) .then(() => createStates()) .then(() => onAnlegen()) .then(() => main()) // test functions .catch(error => _W(_O(error))) .then(() => _I(`Finished Initialization and running ${instanz}${name}`));
Habt nun viel Spaß mit dem code und in meinem nächsen Posting werd ich dann etwas tiefer auf die Anwendung von Promise's eingehen und Beispiele zeigen wie einfach es wird Funktionen zu implementieren.
p.s.: Meine Adapter verwenden die gleiche Technik. Ich teste die meisten Funktionen in javascript und baue sie dann in den Adapter ein wenn sie funktioniert haben. Werde das in einem weiteren Posting mit dem Beispiel von arp-scan für den Radar-Adapter zeigen, die 11 Zeilen testcode ist oben schon enthalten aber auskommentiert und noch ohne Kommentar.
p.p.s.: mußte den globalen code leicht ändern da ich auch auf der jetzt aktuellen nodejs Verion 6.9.1 getestet habe und dort sind schon mehr Funktionen von ES2015 standardmäßig aktiv wodurch ein staement nicht mehr funktioniert hat. Habe nebenbei auch pRepeat upgedated welches jetzt wie pSeries ein Array mit den rückgabewerten returniert.
Liebe Grüße, Frank
-
-
Wie versprochen wollte ich einige der Methoden erklären und werd da mit den ersten Zeilen von main beginnen.
debuglog = 'info'; function main() { _D(`start main von script ${name}`); // logge aud debug dass main started printSubs(name); // zeige subscriptions an - das brauch ich immer nur am Anfang und das wird später rauskommentiert return pSst(instanz+alarmPot, "alarmTest") // Setze state alarmPot auf "alarmTest" um zu sehen ob unser on funktioniert .then(() => pSst(instanz+alarmState, 0)) // Setze state alarmState auf 0 (= Aus) um zu sehen ob unser on funktioniert .then(() => pExec('whoami')) // rufe 'whoami' auf um zu sehen als welcher Benutzer dieses script läuft, geht auch auf Windows bei mir .then(iAm => _I(`I am ${iAm}`,iAm)) // zeige an was pExec zurückgegeben hat
Die Funktion _D() entspricht in etwa der Funktion log(text,debuglog); gibt den Text (in disem Fall ist es ein Template-String mit einem Template ${name}) auf 'debug' in blau aus. Wird die Variable debuglog auf 'info' gesetzt wird der auf Info auch ausgegeben, das ist ist gut so damit man den ganzen Adapter nicht unbedingt auf debug stellen muß um ein script zu testen.
_D('text',val) (genauso wie _I und _W) kann auch ein zweites Argument erhalten welches als dann von _D() zurückgegeben wird. Ist es nicht vorhanden wird der angezeigte Text zurückgegeben.
Das ist wichtig wenn ich Debug/Info/oder Warnings in einem Funktionsaufruf einbauen will wie in der iAm-Zeile, da dort ein ein Text 'I am xxxx' angezeigt wird und xxxx dann zurückgegeben wird.
Die Printsubs-Funktion kennt ihr sicher schon, sie gibt alle subscriptions mit _D() aus.
Nun kommt schon das Return-statement! Aber warum?
Da müssen wir zuerst was über Promises lernen.
Die Zeilen
return pSst(instanz+alarmPot, "alarmTest") // Setze state alarmPot auf "alarmTest" um zu sehen ob unser on funktioniert .then(() => pSst(instanz+alarmState, 0)) // Setze state alarmState auf 0 (= Aus) um zu sehen ob unser on funktioniert .then(() => pExec('whoami')) // rufe 'whoami' auf um zu sehen als welcher Benutzer dieses script läuft, geht auch auf Windows bei mir .then(iAm => _I(`I am ${iAm}`,iAm)) // zeige an was pExec zurückgegeben hat .catch(err => _D(`Error: ${_O(err)}`)) // Zeige Fehler an wenn eine der Aufrufe oben einen Fehler hat // würden ohne Promises mit normalen Bordmitteln etwa so aussehen: setState(instanz+alarmPot,"alarmTest", function(err,obj) { if (err) { log('Error ' + err ,'info'); return; } setState(instanz+alarmState,0, (err,obj) => { // habe hier die neue Arrow-Funktion verwendet if (err) { log('Error ' + err ,'info'); return; } exec('whoami',(err,stdout,stderr) => { if (err) { log('Error ' + err ,'info'); return; } log('I am '+ stdout); }); }); });
Das ist die Callback-Hölle.
Eine Promise ist ein Object das eine Funktion ausführt und dann diese Promise als Versprechen zurück gibt. Das Versprechen wird entweder 'resolved' oder 'rejected'.
Die Promise hat auch funktionen:
then(resolvefun,rejectedfun)
undcatch(rejectedfun)
.bei
then(resolvefun,rejectedfun)
wird die funktionresolvefun(resolved_argument)
bei erfolg ausgeführt und falls vorhandenrejectedfun(rejected_argument)
wenn ein Fehler war.Wenn keine rejectedfun angegeben wird werden bei Fehlern mit der nächsten
.catch()
oder nächsen then mit rejectedfun fortgesetzt.In unserem Fall würde bei einem Fehler im pExec bei .catch weitergemacht und iAm nicht ausgegeben werden. Versucht es mal wenn ihr 'whoami' durch 'xwhoami' ersetzt…
Also das .then steht in der Kette sin etwa so wie der callback da es erst ausgeführt wird wenn der callback passiert.
Im Beispiel werden alle Befehle strikt nacheinander ausgeführt, und auch nur dann wenn das vorige keinen Fehler hatte.
Nun, die resolve- und reject Funktionen können Werte returnieren die als einziges Argument der den nächsten resolve/reject-Funktionen weitergegeben und in eine neue Promise umgewandelt wird um die Verkettung zu schaffen.
Man kann auch eine Promise zurückgeben die dann an die Kette angehängt wird. Deshalb hab ich vor die Kette das Return-Statement gesetzt wodurch main eine Promise zurückgibt die sich erfüllt wenn die gesamte Kette durchlaufen ist. damit kann main selbst in eine Kette eingebunden werden.
Diejenigen von euch die sich den code angesehen haben haben vielleicht die Funktion
wait(time,arg)
gefunden. Sie ist eine Promise die time ms wartet und dann die Promise mit dem Argument arg erfüllt.Wenn man nach der .chatch-Zeile
.then(() => wait(100)) // warte 100ms .then(() => _D('end main')) // gib text aus und schließe main ab
einfügt würde das Programm durchlaufen werden dabei würde .catch nicht ausgeführt wenn kein Fehler auftritt und es würde dann 100ms warten und 'ende main' ausgeben.
Somit könnte man auch main in Sequenz aufrufen:
main() .then(() => main()) .then(() => main());
würde main 3x hintereinander durchlaufen.
Findige script-Leser haben vielleicht die Funktion pRepeat entdeckt, mit
pRepeat(10,main)
fürde main dann 10x hintereinander ausgeführt!
So, jetzt genug für heute. Nächstes mal schauen wir uns pSeries an und was man sonst noch so alles mit wenigen Zeilen code machen kann.
-
sehr sehr cool!!!
habe schon ein schlechtes Gewissen hier zu posten, wg. Unterbrechung der Kette der nächsten Posts von Dir:-)
Gesendet von iPhone mit Tapatalk
-
Hallo ruhr!
Kein Problem, können/sollen auch sehr wohl Fragen gestellt werden! Man ist ja selbst immer 'betriebsblind'.
Nach der kleinen Zwangspause gehts weiter.
Habe eins vergessen: Wie schaut ihr welche Node-Version ihr habt?
mit node -v geht das ganz leicht.
Hatte gestern einen Raspi 3 mit dem neuen Jessie-Image von letzter Woche gestartet und war geschockt dass da noch 0.10.irgendwas dabei war. Das wird in einigen Wochen nicht mehr supported!
Um am Raspi (oder anderen Debian Linux) ein neueres node zu installieren lädt man die nodesource mit
curl -sL https://deb.nodesource.com/setup_4.x | sudo -E bash - oder curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash - sudo apt-get install nodejs
Die 6er ist die letztgültige Long-Time-Support-Version (Boron). Es gibt auch 7er aber das ist nur für developers da sie nach Erscheinen der 8ter (nächstes halbes Jahr) dann nicht mehr supported werden.
Unter Windows rate ich auf https://nodejs.org/de/download/ sich die letzte oder andere Versionen wie 4.6.x dort zu suchen https://nodejs.org/en/download/releases/.
Ja, so machen wir mal weiter. In vielen Scripten muss man Variablen anlegen.
Ich habe mir das so angewöhnt:
function createStates () { "use strict"; // Die Funktion returniert eine Promise damit sie verschachtelt werden kann!, 'use strict' erlaubt verwendung von let usw. _D("Create States"); const states = [ // Ich kreiere eine objekt-Array mit allen zu erzeugenden Objekten { id: alarm, val: false, common: {type: 'boolean',name:alarm, unit: '',role: 'state',write:true}}, { id: alarmPot, val: "", common: {type: 'string',name:alarmPot, unit: '',role: 'state',write:true}}, { id: alarmState, val: 0, common: {type: 'number',name:alarmState, unit: '',role: 'state',write:true,max:2,min:0,states:'0:Aus;1:Nacht;2:Ein'}}, ]; for (let i=0; i<3;i++) // muss 3 Variablem programmatisch anlegen, können ja auch von aussen kommen states.push({ id: dpPfad+'Var_'+i , val: i, common: {type: 'number',name:`${dpPfad}Variable ${i}`, unit: '',role: 'state',write:true}}); return pSeries(states, (item) => pCst(item.id,item.val,item.common,{}).then(() => item.id), 10) // cSt ist die in Promise umgewandelte Form von createState, ruft pSeries ruft es für jedes array-item auf und fügt zwischen den items 10ms Pause ein .then(x => wait(100,_D('end created states:'+_O(x),x))); // am Ende wird die Liste der angelegten ID's ausgegeben und 100ms gewartet }
Damit erzeuge ich erstmals ein array von Objekten die die Variablen beschreiben, entweder hard-coded oder programmatisch und dann kreiere ich alle Variablen mittels pSeries.
pSeries(iterable, functionPromise, delay) durchläuft die iterable (kann ein Objekt oder Array sein) und ruft functionPromis(item) für jedes item auf. Die Funktion muß eione Promise zurückgeben. Falls delay angegeben wird wird so viele ms gewartet und dann (sonst sofort) das nächste item aufgerufen.
der Teil '(item) => pCst(item.id,item.val,item.common,{}).then(() => item.id)' verwendet die umgewandelte createState und gibt eine id and pSeries zurück.
pSeries sammelt alle returnbieren Arguimente und wenn es durch ist giebt sie al array zurück.
Damit kann '.then(x =>…' dieses Array anzeigen, warten und selbst dann zurückgegeben werden.
Am Ende unseres scripts steht die Folge:
wait(100) .then(() => createStates()) .then(() => onAnlegen()) // ist ähnlich wie createStates aufgebaut. .then(() => main()) // test functions .catch(error => _W(_O(error))) // war da ein Fehler? .then(() => _I(`Finished Initialization and running ${instanz}${name}`));
Damit wird beim Start des scriptes zuerst 100ms gewartet, dann dies Variablen kreiert, dann onAnlegen ausgeführt, dann main ausgeführte, dann auf eventuellen Fehlern in einigen der vorigen Routinen geprüft und dann die info ausgegeben dass das Script jetzt läuft!
Was kann man sonst noch machen?
Ich zeig mal ein Beispiel welches fast gleich ich einen Adapter eingeflossen ist (hab dort nur einen cache eingebaut um nicht alle x Sekunden das selbe abzufragen):
arp-scan kann unter linux mit sudo apt-get install arp-scan installiert werden uns scannt das lokale Netz nach angeschlossenen/laufenden Geräten.
const dns = require('dns'); // inkludiere die 'dns'-funktionen pExec('arp-scan -lgq --retry=10') // rufe arp-scan auf .then(res => res && res.match(/(\d*\.){3}\d*\s*([\dA-F]{2}\:){5}[\dA-F]{2}/gi)) // gib ein Array aus die aus den IP und MAC-Adressen besteht die gefunden wurden .then(res => pSeries(res, item => { // Für jedes gefundene IP/MAC-Paar const s = item.split('\t'); // splitte die IP von der MAC-Adresse, diese sind durch \t getrennt return pGet('http://api.macvendors.com/'+s[1]) // nun prüfe mittels einer web-Api wer der Erzeuge für die HW ist mit dieser Mac-Adresse .then(x => x.trim(), err => 'Vendor not found') // wenn ein Fehler auftritt und der Hersteller nicht gefunden wird gib 'Vendor not found' zurück .then(x => s.push(x) && c2pP(dns.reverse)(s[0])) // hänge an das split array den Herstellernamen an und versuche einen dns.reverse call mit der IP-Adresse um den Namen am lokalen DNS-Server zu erfahren .then(nam => s.concat(nam), err => s.concat(['N/A'])) // Hänge den Namen oder 'N/A' an wenn er nicht gefunden wurde .then(res => res.join('; ')); // Mach aus dem Array ein String mit '; ' getrennt },10)) // warte 10ms nach jedem gefundenen Paar .then(res => pSeries(res,item => _D(_O(item),Promise.resolve()))); // Zeig alle Ergebnisse an. Promise.resolve returniert eine Promise die sofort 'Erfolg' bestätigt.
Warum verwende ich lieber const/let? Es reduziert die Proigrammierfehler!
Mit const s = xxx sag ich dass ich s diesen Block definiere und es nicht neu zugewiesen werden darf.
Mit s.push() kann ich den Inhalt veändern aber ich darf s nich mit s = x neu zuweisen.
Mit let definiert man variablen die auch nur in diesem Block gültig sind, also ist
`var k = [1,2,3]; for (let i in k) { let j = k[i]; for(let k of [11,22,33]) _D(`${k}:${j}`); } _D(k);` immer eine neue Variable i,j und k und das 'var k' bleibt so wie es ist. Aber Vorsicht: Wenn ihr Node Version 4.x oder darunter (0.12) verwendet ist let nur anwendbar wenn '"use strict";' auf Funktionslevel angewendet wird. Wenn ihr Version 6.9.x verwendet (die momentane Note-LTS = Long Time Support-Version) dann ist let auch im gesamten schript ohne '"use strict";' möglich. In einer weiteren Folge weden wir dann einige weiteren Tricks beleuchten! Liebe Grüße Frank[/i]