Prácticas recomendadas para la codificación en JavaScript
Organización y formato del código
Escriba la lógica de código en la parte superior y las funciones de ayuda al final.
Declare las variables globales en la parte superior.
Si hay más de 150 líneas en el servicio, divida el código en funciones o en varios servicios.
Asigne los nombres a las variables y funciones adecuadamente para reducir el número de comentarios y mejorar la legibilidad del código.
Utilice la funcionalidad de formato automático de JavaScript integrada en ThingWorx Composer.
Escritura del código
Utilice casos truthy y falsy en sentencias compuestas.
// *** 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
Lleve a cabo operaciones CRUD por lotes para minimizar las interacciones con la base de datos. Sin embargo, si se recibe demasiada información de la base de datos, es posible que desee realizar varias llamadas únicas a la base de datos.
// *** 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.
Evite manipular las propiedades de cosa directamente, ya que puede provocar problemas de sincronización persistentes y la pérdida de rendimiento.
// *** 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.
Para suprimir el análisis lint en la siguiente línea, añada un comentario. De este modo, se eluden los errores del análisis lint que solo podían solucionarse mediante la corrección de la biblioteca de análisis lint y seguir obteniendo el análisis lint en todas partes.
Objetos
Cree objetos mediante {} en lugar de new Object();. Intente crear los valores de objeto en línea porque son más fáciles de leer y más rápidos de escribir.
// *** 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"
};
En el caso de los objetos de clonación profunda, utilice JSON.parse(JSON.stringify(object)).
Variables
Declare las variables con let en lugar de var. Utilice const para declarar las variables constantes.
Si se usa let se evita volver a declarar variables y sobrescribirlas. También se evita la creación de funciones con el mismo nombre.
// *** 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
Utilice CamelCase para asignar un nombre a las variables.
Para definir un valor por defecto para una variable de un valor undefined o null, utilice el operador || (O).
// *** 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";
* 
Puede que no sea la manera ideal de definir un valor por defecto porque utiliza una sentencia falsy. Por ejemplo, si un valor es 0, el operador || podría definir otro valor en lugar de 0.
Utilice la notación de puntos para acceder a las variables. Utilice la notación de corchetes solo cuando haya caracteres especiales en la clave, por ejemplo: object.row["field name with space"].
Cree una variable para almacenar una cosa y utilice dicha variable en un bucle.
// *** 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;
}
Funciones
Utilice CamelCase para asignar el nombre a funciones.
Declare las funciones como declaraciones de función y no como variables.
No es posible mover expresiones de función ni expresiones de flecha de función a la parte inferior del código.
// *** 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;
}
Utilice return en funciones para interrumpir el flujo de la lógica de código. Con ello se ayuda a guardar algunas operaciones de CPU y a promover la legibilidad.
// 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.
Servicios
Use PascalCase para asignar nombres a los servicios.
Utilice un parámetro en línea para llamar a los servicios. Sin embargo, si los parámetros de entrada son grandes o si desea reutilizarlos, almacene los parámetros de entrada como una variable y, a continuación, pase la variable a la llamada de servicio.
// *** old practice ***
var params = {
"param1" : "paramValue1",
"param2" : "paramValue2"
}
Thing["thingName"].CallServiceName(params);
// *** preferred practice ***
Thing["thingName"].CallServiceName({
"param1" : "paramValue1",
"param2" : "paramValue2"
});
Evite utilizar servicios que llamen a la base de datos dentro de un bucle.
Infotables
Utilice el siguiente método para crear una infotable:
// common way
var table = Resources["InfoTableFunctions"].CreateInfoTableFromDataShape({
infoTableName : "InfoTable",
dataShapeName : "GenericStringList"
});
// easier way
var table = DataShapes["GenericStringList"].CreateValues();
Utilice el siguiente método para obtener la longitud o la fila de una infotable:
// fetching a row
myInfoTable[i] = myInfoTable.rows[i] = myInfoTable.getRow(i)
// getting the length
myInfoTable.length = myInfoTable.rows.length = myInfoTable.getRowCount()
Utilice la notación de puntos para acceder a los datos de la primera fila de la infotable:
// 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
Utilice métodos de matriz, como .map(), .reduce(), .filter(), .some() y .every(), para manipular las infotables o para realizar cambios en varias filas de la infotable. Estos métodos se pueden utilizar para infotables si se utiliza infotable.rows.toArray().[map() | reduce() | filter()].
Uso de .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;
Aplicación de cambios a varias filas de la infotable:
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;
Uso de .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);
Para iterar y crear bucles en los datos de infotables, utilice un bucle .forEach(). De este modo, se mejora el rendimiento.
table.rows.toArray().forEach(row => {
data += row.item;
});
Para infotables de clonación profunda, utilice var deepClonedInfoTable = myInfoTable.clone().
Mensajes del registrador
En las sentencias try … catch, asigne el nombre err al objeto de excepción.
try {
Things["thingName"].CallServiceName();
} catch (err) {
logger.error(err);
} finally { // finally is optional
// will go here whether an exception occured or not
}
* 
No se recomienda registrar el error (err en este caso) en un formato JSON y err.message se debe utilizar en su lugar como práctica recomendada.
Siempre que se cree una cosa, se debe rodear con una sentencia try catch y borrarla en caso de fallo en la sentencia catch. De este modo, se elimina la creación de entidades fantasma. Si hay entidades fantasma en el sistema, consulte Ghost Entity Services para borrar las entidades fantasma.
Siempre que tenga un servicio que cree entidades, asegúrese de poder gestionar el caso de uso de entidades fantasma.
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 */
});
}
Si hay registros de error que se deben mostrar en los registros del script de supervisión, se debe incluir información como, por ejemplo, el nombre de la cosa, el nombre del servicio, el número de línea y el mensaje de error original. Recuerde que se puede utilizar {} como marcador para el formato de la cadena en el registro. Se debe tener en cuenta que el mensaje formateado puede revelar información innecesaria que podría provocar problemas de seguridad. Evite mostrar demasiados detalles si se trata de servicios que se utilizan en un mashup o en sistemas externos.
Las siguientes propiedades están disponibles en un objeto de excepción:
err.lineNumber: número de línea donde se ha producido la excepción.
err.name: tipo de excepción, por ejemplo, JavaException.
err.fileName: nombre del servicio desde el que se ha iniciado la excepción.
err.message: mensaje de excepción.
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.
En función de las necesidades de granularidad, utilice el nivel de registrador adecuado.
Escriba los registradores solo cuando sea necesario. Demasiados mensajes de registro imposibilitan la depuración.
Nivel
Descripción
DEBUG
Permite especificar eventos informativos detallados que son más útiles para depurar una aplicación.
// 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
Permite especificar mensajes informativos que realzan el progreso de la aplicación en un nivel más impreciso.
// 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
Permite especificar situaciones potencialmente dañinas.
// 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
Permite especificar eventos de error que pueden permitir que la aplicación siga su ejecución.
// 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);
}
¿Fue esto útil?