Optimale Vorgehensweisen für das Codieren in JavaScript
Code organisieren und formatieren
Schreiben Sie Code-Logik am Anfang und Hilfsfunktionen am Ende.
Deklarieren Sie globale Variablen am Anfang.
Wenn Ihr Dienst mehr als 150 Zeilen umfasst, teilen Sie den Code in Funktionen oder mehrere Dienste auf.
Benennen Sie die Variablen und Funktionen entsprechend, um die Anzahl der Kommentare zu reduzieren und die Lesbarkeit des Codes zu verbessern.
Verwenden Sie die integrierte JavaScript-AutoFormat-Funktion in ThingWorx Composer.
Code schreiben
Verwenden Sie truthy- und falsy-Fälle in bedingten Anweisungen.
// *** will act like a false statement ***
if (false)
if (null)
if (undefined)
if (0)
if (-0)
if (0n)
if (NaN)
if ("")
// *** will act like a true statement ***
if (true)
if ({})
if ([])
if (1)
if ("0")
if ("false")
if (new Date())
if (-1)
if (12n)
if (3.14)
if (-3.14)
if (Infinity)
if (-Infinity)
// *** you can force it into a boolean value by using the !! operator ***
// i.e.:
var myNullValue = null;
var myBooleanValue = !!myNullValue; // it will store as false
Führen Sie CRUD-Operationen im Batch aus, um Ihre Interaktionen mit der Datenbank zu minimieren. Wenn Sie jedoch zu viele Informationen von der Datenbank erhalten, können Sie mehrere einzelne Aufrufe an die Datenbank senden.
// *** bad practice ***
for (let i = 0; i < Things["thingName"].GetPropertyDefinitions().length; i++) {
var newEntry = new Object();
newEntry.name = Things["thingName"].GetPropertyDefinitions().rows[i].name;
newEntry.description = Things["thingName"].GetPropertyDefinitions().rows[i].description;
result.AddRow(newEntry);
}
// GetPropertyDefinitions() is called multiple times because of the looping

// *** preferred practice ***
let propertyDefinitions = Things["thingName"].GetPropertyDefinitions();
propertyDefinitions.rows.toArray().forEach((propertyDefinition) => {
result.AddRow({
name : propertyDefinition.name,
description : propertyDefinition.description
});
});
// By storing the content of GetPropertyDefinitions() ahead of time, you will prevent many unnecessary REST calls.
Vermeiden Sie die direkte Bearbeitung von Dingeigenschaften, da dies zu dauerhaften Synchronisierungsproblemen und Leistungseinbußen führen kann.
// *** common practice ***
var referencedInfoTable = Things["thingName"].myInfoTable;
for (let i = 0; i < 10; i++) {
var row = new Object();
row.field1 = i;
row.field2 = i * 2;
row.field3 = i * 3;
referencedInfoTable.AddRow(row);
}
Things["thingName"].myInfoTable = referencedInfoTable;
// the .AddRow() will implicitely write to cache, thus causing some performance issues

// *** preferred practice ***
var deepCloneInfoTable = Things["thingName"].myInfoTable.clone();
for (let i = 0; i < 10; i++) {
deepCloneInfoTable.AddRow({
field1 : i,
field2 : i * 2,
field3 : i * 3
});
}
Things["thingName"].myInfoTable = deepCloneInfoTable;
// the .AddRow() of the cloned infotable does not have a reference to the original infotable, therefore does not write to cache.

// *** for your information ***
var referencedInfoTable = Things["thingName"].myInfoTable;
var tableLength = referencedInfoTable.rows.length;
for (let i = 0; i < tableLength; i++) {
referencedInfoTable.rows[i] = {
field1 : i,
field2 : i * 2,
field3 : i * 3
};
}
Things["thingName"].myInfoTable = referencedInfoTable;
// because you are not using .AddRow() it doesn't require access to the referenced object's .AddRow() method, therefore it doesn't need to access the cache.
Weitere Informationen finden Sie unter Optimale Vorgehensweisen für HA-Anwendungen.
Fügen Sie einen Kommentar hinzu, um Linting in der nächsten Zeile zu unterdrücken. Dadurch werden die Linting-Fehler umgangen, die nur behoben werden konnten, indem die Linting-Bibliothek repariert wurde, wonach überall sonst trotzdem weiterhin Linting auftrat.
Objekte
Erstellen Sie Objekte mit {} statt mit new Object();. Versuchen Sie, Objektwerte inline zu erstellen, da dies einfacher zu lesen und schneller zu schreiben ist.
// *** old practice ***
var myObject = new Object();
myObject.item1 = "value1";
myObject.item2 = "value2";
myObject.item3 = "value3";
// *** preferred practice ***
var myObject = {
item1:"value1",
item2:"value2",
item3:"value3"
};
Verwenden Sie für Deep Cloning-Objekte JSON.parse(JSON.stringify(object)).
Variablen
Deklarieren Sie die Variablen mit let statt mit var. Verwenden Sie const zum Deklarieren konstanter Variablen.
Bei Verwendung von let können Sie Ihre Variablen nicht erneut deklarieren oder überschreiben. Auch die Erstellung gleichnamiger Funktionen ist nicht möglich.
// *** common mistake ***
var value = 1;
if (true) {
var value = 2;
}
// value = 2;because it was overwritten by `value` inside if statement
// *** how to avoid mistake ***
let value = 1;
if (true) {
let value = 2;
// value = 2; inside the if statement
}
// value = 1;because the `value` inside if statement does not affect the first `value`

// *** you can still access the variable in the outter scope ***
let value = 1;
if (true) {
value = 2;
}
// value = 2;because the content of `value` was replaced
Verwenden Sie für Variablennamen die Camel-Case-Schreibweise.
Wenn Sie einen Standardwert für eine Variable für einen undefined- oder null-Wert festlegen möchten, verwenden Sie den Operator || (ODER).
// *** traditional way ***
var myNullValue; //undefined
if (myNullValue == null || myNullValue == undefined || myNullValue == "") {
myNullValue = "something";
}
var output = myNullValue;
// *** alternate way with ternary operator ***
var myNullValue; //undefined
var output = (myNullValue == null || myNullValue == undefined || myNullValue == "") ? "something" : myNullValue;
// *** preferred practice ***
var myNullValue; //undefined
var output = myNullValue || "something";
* 
Dies ist möglicherweise nicht die ideale Methode zum Festlegen eines Standardwerts, da hierbei eine falsy-Anweisung verwendet wird. Wenn Sie beispielsweise einen 0-Wert haben, legt der Operator || möglicherweise einen anderen Wert als 0 fest.
Verwenden Sie für den Zugriff auf Variablen die Punktnotation. Verwenden Sie die Klammernotation nur, wenn der Schlüssel Sonderzeichen enthält. Beispiel: object.row["field name with space"].
Erstellen Sie eine Variable zum Speichern eines Dings, und verwenden Sie diese Variable in einer Schleife.
// *** common mistake ***
for (let i = 0; i < 100; i++ ){
Things["thingName"].myProperty;
}
// *** best practice practice ***
let myThing = Things["thingName"];
for (let i = 0; i < 100; i++){
myThing.myProperty;
}
Funktionen
Verwenden Sie für Funktionsnamen die Camel-Case-Schreibweise.
Deklarieren Sie Ihre Funktionen als Funktionsdeklarationen und nicht als Variablen.
Funktionsausdrücke und Arrow-Funktionsausdrücke können nicht an das Ende des Codes verschoben werden.
// *** function declaration (Preferred practice) ***
function doSomething1 (param) {
return param;
}
// *** function expression (Avoid) ***
const doSomething2 = function (param) {
return param;
}
// *** function arrow expression (Can be used inside .map(), .redude(), .filter()) ***
const doSomething3 = (param) => {
return param;
}
Nutzen Sie return in Funktionen, um den Fluss der Codelogik zu unterbrechen. Dadurch können einige CPU-Operationen eingespart und die Lesbarkeit verbessert werden.
// given that we have multiple validations, you can save some operations by leveraging a function return
// given dice1 = undefined, dice2 = null
// common way
let outputMessage;
if (dice1 > 0 && dice1 < 7) {
if (dice2 > 0 && dice2 < 7) {
if (dice1 + dice2 == 7) {
outputMessage = SUCCESS;
} else {
outputMessage = ERROR_NOT_SEVEN;
}
} else {
outputMessage = ERROR_BAD_DICE2;
}
} else {
outputMessage = ERROR_BAD_DICE1;
}
result = outputMessage;
// preferred way
result = rollSeven(dice1, dice2);
function rollSeven (dice1, dice2) {
if (!(dice1 > 0 && dice1 < 7)) {
return ERROR_BAD_DICE1;
}

if (!(dice2 > 0 && dice2 < 7)) {
return ERROR_BAD_DICE2;
}

if (dice1 + dice2 != 7) {
return ERROR_NOT_SEVEN;
}

return SUCCESS;
}
// the preferred way promotes readability. By using a return statement and only testing for failing cases it reduces the need for nested if statements.
Dienste
Verwenden Sie für Dienstnamen die Pascal-Case-Schreibweise.
Verwenden Sie einen Inline-Parameter zum Aufrufen von Diensten. Im Fall von großen Eingabeparametern oder wenn Sie die Parameter wiederverwenden möchten, speichern Sie die Eingabeparameter als Variable, und übergeben Sie die Variable dann an den Dienstaufruf.
// *** old practice ***
var params = {
"param1" : "paramValue1",
"param2" : "paramValue2"
}
Thing["thingName"].CallServiceName(params);
// *** preferred practice ***
Thing["thingName"].CallServiceName({
"param1" : "paramValue1",
"param2" : "paramValue2"
});
Vermeiden Sie die Verwendung von Diensten, die die Datenbank innerhalb einer Schleife aufrufen.
Infotables
Verwenden Sie die folgende Methode, um eine Infotable zu erstellen:
// common way
var table = Resources["InfoTableFunctions"].CreateInfoTableFromDataShape({
infoTableName : "InfoTable",
dataShapeName : "GenericStringList"
});
// easier way
var table = DataShapes["GenericStringList"].CreateValues();
Verwenden Sie die folgende Methode, um die Länge oder Zeile einer Infotable abzurufen:
// fetching a row
myInfoTable[i] = myInfoTable.rows[i] = myInfoTable.getRow(i)
// getting the length
myInfoTable.length = myInfoTable.rows.length = myInfoTable.getRowCount()
Verwenden Sie die Punktnotation, um auf die Daten der ersten Zeile der Infotable zuzugreifen:
// if you have an infotable with rows
// classical way to access the first row field
myInfoTable.row[0].field
// trick to access the first row field
myInfoTable.field
Verwenden Sie Array-Methoden wie .map(), .reduce(), .filter(), .some() und .every(), um Infotables zu bearbeiten oder Änderungen an mehreren Infotable-Zeilen vorzunehmen. Sie können diese Methoden für Infotables verwenden, wenn Sie infotable.rows.toArray().[map() | reduce() | filter()] verwenden.
Bei Verwendung von .map():
// *** What you have ***
var officers = [
{ id: 20, name: 'Captain' },
{ id: 24, name: 'General' },
{ id: 56, name: 'Admiral' },
{ id: 88, name: 'Commander' }
];
// *** What you need ***
//[39, 44, 57, 95]
var officersIds = officers.map((officer) => officer.id);
result = officersIds;
Änderungen auf mehrere Infotable-Zeilen anwenden:
var table = Resources["InfoTableFunctions"].CreateInfoTableFromDataShape({
infoTableName : "InfoTable",
dataShapeName : "GenericStringList"
});
table.AddRow({item:1});
table.AddRow({item:2});
table.AddRow({item:3});
table.AddRow({item:4});
Array.from(table.rows).map(row => {row.item = row.item * 2 + "hello";});
result = table;
Bei Verwendung von .reduce():
var table = Resources["InfoTableFunctions"].CreateInfoTableFromDataShape({
infoTableName : "InfoTable",
dataShapeName : "GenericStringList"
});
table.AddRow({item:1});
table.AddRow({item:2});
table.AddRow({item:3});
table.AddRow({item:4});
// total sum should be 1 + 2 + 3 + 4 = 10
result = Array.from(table.rows).reduce((total, row) => total + parseInt(row.item), 0);
Um Daten in Infotables zu iterieren und zu durchlaufen, verwenden Sie eine .forEach()-Schleife. Dies verbessert die Leistung.
table.rows.toArray().forEach(row => {
data += row.item;
});
Verwenden Sie für Deep Cloning-Infotables var deepClonedInfoTable = myInfoTable.clone().
Protokollierermeldungen
Benennen Sie das Ausnahmeobjekt in try … catch-Anweisungen als err.
try {
Things["thingName"].CallServiceName();
} catch (err) {
logger.error(err);
} finally { // finally is optional
// will go here whether an exception occured or not
}
* 
Das Protokollieren des Fehlers (in diesem Fall err) in ein JSON-Format wird nicht empfohlen. Als optimale Vorgehensweise muss stattdessen err.message verwendet werden.
Wenn Sie ein Ding erstellen, umgeben Sie es immer mit einer try catch-Anweisung, und löschen Sie es, wenn die catch-Anweisung fehlschlägt. Dadurch wird die Erzeugung von Ghost-Entitäten verhindert. Wenn Ihr System Ghost-Entitäten enthält, finden Sie unter Ghost-Entitätsdienste Informationen zum Löschen der Ghost-Entitäten.
Stellen Sie für jeden Dienst, der eine Entität erstellt, sicher, dass Sie einen Anwendungsfall für Ghost-Entitäten behandeln.
try {
// Successfully creates the GhostThing entity.
Resources["EntityServices"].CreateThing({
name: "GhostThing" /* STRING */,
description: "Ghost" /* STRING */,
thingTemplateName: "GenericThing" /* THINGTEMPLATENAME */
});
} catch (err) {
// If an exception is caught, we need to delete the entity
// that was created to prevent it from becoming a Ghost Entity
Resources["EntityServices"].DeleteThing({
name: "GhostThing" /* THINGNAME */
});
}
Wenn Fehlerprotokolle vorhanden sind, die in den Skriptprotokollen der Überwachung angezeigt werden sollen, schließen Sie Informationen wie den Dingnamen, den Dienstnamen, die Zeilennummer und die ursprüngliche Fehlermeldung ein. Beachten Sie, dass Sie {} als Platzhalter für die Zeichenfolgenformatierung im Protokoll verwenden können. Denken Sie daran, dass die formatierte Meldung unnötige Informationen anzeigen kann, die möglicherweise zu Sicherheitsproblemen führen. Vermeiden Sie die Anzeige von zu vielen Details, wenn Sie mit Diensten arbeiten, die normalerweise in einem Mashup oder externen Systemen verwendet werden.
Die folgenden Eigenschaften stehen in einem Ausnahmeobjekt zur Verfügung:
err.lineNumber – Zeilennummer, in der die Ausnahme ausgelöst wurde.
err.name – Ausnahmetyp, z.B. JavaException.
err.fileName – Name des Diensts, von dem die Ausnahme ausgelöst wurde.
err.message – Ausnahmemeldung.
try {
// error is fired here
} catch (err) {
let errMsg = "Thing [{}] Service [{}] error at line [{}] : {}";
logger.error(errMsg, me.name, err.fileName, err.lineNumber, err);
throw err; // throw original error message
}
// log output => Thing [PTC.SCA.SCO.MyThing] Service [CallMyService] error at line [2]: TypeError: Cannot find function CallMyService in object com.thingworx.things.MyThing@251c8397.
Verwenden Sie die richtige Protokolliererebene für ihre jeweiligen Granularitätsanforderungen.
Schreiben Sie Protokollierer nur bei Bedarf. Zu viele Protokollmeldungen machen das Debuggen unmöglich.
Ebene
Beschreibung
DEBUG
Geben Sie genaue Informationsereignisse an, die für das Debuggen einer Anwendung am nützlichsten sind.
// for example, show the length of time a function takes to run.
let startTime = new Date().getTime();
// do something
let endTime = new Date().getTime();
let time = endTime - startTime;
logger.debug("How long did it take to run something (ms) : {}", time);
INFO
Geben Sie Informationsmeldungen an, die den Fortschritt der Anwendung auf allgemeiner Ebene hervorheben.
// for example, you wish to track the progress of your service
logger.info("Step 1 - creating variable");
let step1 = "do something";
logger.info("Step 2 - run a function");
myFunctionDoSomething(step1);
WARN
Geben Sie potenziell gefährliche Situationen an.
// for example, you see a potential issue here
let resultFromService = Things["thingName"].CallServiceName();
if (resultFromService == undefined) {
logger.warn("My variable is undefined... strange");
}
myFunctionDoSomething(resultFromService);
ERROR
Geben Sie Fehlerereignisse an, bei denen die Anwendung möglicherweise weiterhin ausgeführt werden kann.
// for example, a service was not suppose to throw an error
try {
var resultFromService = Things["thingName"].CallServiceName();
} catch (err) {
logger.error("Something really bad happened: {}", err);
}
War dies hilfreich?