Guía para desarrolladores de Node.js de Azure Functions

Esta guía es una introducción al desarrollo de Azure Functions con JavaScript o TypeScript. En este artículo se supone que ya ha leído la guía para desarrolladores de Azure Functions.

Importante

El contenido de este artículo cambia en función de la elección del modelo de programación de Node.js en el selector de la parte superior de esta página. La versión que elija debe coincidir con la versión del paquete npm @azure/functions que está usando en la aplicación. Si no le aparece ese paquete en package.json, el valor predeterminado es v3. Obtenga más información sobre las diferencias entre v3 y v4 en la guía de migración.

Como desarrollador de Node.js, puede que también le interese uno de los siguientes artículos:

Introducción Conceptos Aprendizaje guiado

Consideraciones

  • El modelo de programación de Node.js no debe confundirse con el runtime de Azure Functions:
    • Modelo de programación: define cómo crea el código y es específico de JavaScript y TypeScript.
    • Runtime: define el comportamiento subyacente de Azure Functions y se comparte en todos los lenguajes.
  • La versión del modelo de programación está estrictamente vinculada a la versión del paquete npm @azure/functions. Se versiona independientemente del runtime. Tanto el runtime como el modelo de programación usan el número 4 como versión principal más reciente, pero es casualidad.
  • No puede mezclar los modelos de programación de las versiones 3 y 4 en la misma aplicación de funciones. En cuanto registre una función de la versión 4 en la aplicación, se omiten las funciones de la versión 3 registradas en los archivos function.json.

Versiones compatibles

En la tabla siguiente se muestra cada versión del modelo de programación de Node.js junto con sus versiones compatibles del entorno de ejecución y la Node.js de Azure Functions.

Versión del modelo de programación Nivel de soporte técnico Versión del runtime de Functions Versión del Node.js Descripción
4.x GA 4.25+ 20.x, 18.x Admite una estructura de archivos flexible y un enfoque centrado en código para desencadenadores y enlaces.
3.x GA 4.x 20.x, 18.x, 16.x, 14.x Requiere una estructura de archivos específica con los desencadenadores y enlaces declarados en un archivo "function.json"
2.x N/D 3.x 14.x, 12.x, 10.x Llegó al final del soporte técnico el 13 de diciembre de 2022. Consulte Versiones de Functions para obtener más información.
1.x N/D 2.x 10.x, 8.x Llegó al final del soporte técnico el 13 de diciembre de 2022. Consulte Versiones de Functions para obtener más información.

Estructura de carpetas

La estructura de carpetas necesaria para un proyecto de JavaScript se parece al ejemplo siguiente:

<project_root>/
 | - .vscode/
 | - node_modules/
 | - myFirstFunction/
 | | - index.js
 | | - function.json
 | - mySecondFunction/
 | | - index.js
 | | - function.json
 | - .funcignore
 | - host.json
 | - local.settings.json
 | - package.json

La carpeta de proyecto principal, <project_root>, puede contener los siguientes archivos:

  • .vscode/: (Opcional) Contiene la configuración almacenada de Visual Studio Code. Para más información, consulte Configuración de Visual Studio Code.
  • myFirstFunction/function.json: contiene la configuración del desencadenador, las entradas y las salidas de la función. El nombre del directorio determina el nombre de la función.
  • myFirstFunction/index.js: almacena el código de la función. Para cambiar esta ruta de acceso de archivo predeterminada, consulte Uso de scriptFile.
  • .funcignore: (Opcional) declara los archivos que no deben publicarse en Azure. Normalmente, este archivo contiene .vscode/ para omitir la configuración del editor, test/ para omitir los casos de prueba y local.settings.json para evitar la publicación de la configuración de la aplicación local.
  • host.json: contiene las opciones de configuración global que afectan a todas las funciones de una instancia de aplicación de funciones. Este archivo se publica en Azure. No todas las opciones se admiten cuando se ejecuta localmente. Para más información, consulte host.json.
  • local.settings.json: se usa para almacenar las cadenas de conexión y la configuración de la aplicación cuando la ejecución se realiza a nivel local. Este archivo no se publica en Azure. Para más información, consulte local.settings.file.
  • package.json: contiene opciones de configuración como una lista de dependencias de paquete, el punto de entrada principal y los scripts.

La estructura de carpetas recomendada para un proyecto de JavaScript en Azure Functions tiene la siguiente apariencia:

<project_root>/
 | - .vscode/
 | - node_modules/
 | - src/
 | | - functions/
 | | | - myFirstFunction.js
 | | | - mySecondFunction.js
 | - test/
 | | - functions/
 | | | - myFirstFunction.test.js
 | | | - mySecondFunction.test.js
 | - .funcignore
 | - host.json
 | - local.settings.json
 | - package.json

La carpeta de proyecto principal, <project_root>, puede contener los siguientes archivos:

  • .vscode/: (Opcional) Contiene la configuración almacenada de Visual Studio Code. Para más información, consulte Configuración de Visual Studio Code.
  • src/functions/: la ubicación predeterminada para todas las funciones y sus desencadenadores y enlaces relacionados.
  • test/: (opcional) contiene los casos de prueba de la aplicación de funciones.
  • .funcignore: (Opcional) declara los archivos que no deben publicarse en Azure. Normalmente, este archivo contiene .vscode/ para omitir la configuración del editor, test/ para omitir los casos de prueba y local.settings.json para evitar la publicación de la configuración de la aplicación local.
  • host.json: contiene las opciones de configuración global que afectan a todas las funciones de una instancia de aplicación de funciones. Este archivo se publica en Azure. No todas las opciones se admiten cuando se ejecuta localmente. Para más información, consulte host.json.
  • local.settings.json: se usa para almacenar las cadenas de conexión y la configuración de la aplicación cuando la ejecución se realiza a nivel local. Este archivo no se publica en Azure. Para más información, consulte local.settings.file.
  • package.json: contiene opciones de configuración como una lista de dependencias de paquete, el punto de entrada principal y los scripts.

Registro de una función

El modelo v3 registra una función según la existencia de dos archivos. En primer lugar, necesita un archivo function.json ubicado en una carpeta un nivel por debajo de la raíz de la aplicación. En segundo lugar, necesitará un archivo JavaScript que exporte la función. De forma predeterminada, el modelo busca un archivo index.js en la misma carpeta que function.json. Si usa TypeScript, deberá usar la propiedad scriptFile en function.json para que apunte al archivo JavaScript compilado. Para personalizar la ubicación del archivo o el nombre de exportación de la función, consulte la configuración del punto de entrada de la función.

La función que exporta siempre debe declararse como async function en el modelo v3. Puede exportar una función sincrónica, pero debe llamar a context.done() para indicar que la función se ha completado, lo que está en desuso y no se recomienda.

La función recibe una invocación context como primer argumento y las entradas como argumentos restantes.

El ejemplo siguiente es una función simple que registra que se ha desencadenado y responde con Hello, world!:

{
  "bindings": [
    {
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "authLevel": "anonymous",
      "methods": [
        "get",
        "post"
      ]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    }
  ]
}
module.exports = async function (context, request) {
    context.log('Http function was triggered.');
    context.res = { body: 'Hello, world!' };
};

El modelo de programación carga las funciones en función del campo main de package.json. Puede establecer el campo main en un único archivo o en varios archivos mediante un patrón global. La siguiente tabla muestra valores de ejemplo para el campo main:

Ejemplo Descripción
src/index.js Registrar funciones desde un único archivo raíz.
src/functions/*.js Registre cada función desde su propio archivo.
src/{index.js,functions/*.js} Combinación en la que se registra cada función desde su propio archivo, pero todavía tiene un archivo raíz para el código general de nivel de aplicación.

Para registrar una función, debe importar el objeto app desde el módulo npm @azure/functions y llamar al método específico del tipo de desencadenador. El primer argumento al registrar una función es el nombre de la función. El segundo argumento es un objeto options que especifica la configuración del desencadenador, el controlador y cualquier otra entrada o salida. En algunos casos en los que la configuración del desencadenador no sea necesaria, puede pasar el controlador directamente como segundo argumento en lugar de un objeto options.

El registro de una función se puede realizar desde cualquier archivo del proyecto, siempre y cuando ese archivo se cargue (directa o indirectamente) en función del campo main del archivo package.json. La función debe registrarse en un ámbito global porque no se pueden registrar funciones una vez iniciadas las ejecuciones.

El ejemplo siguiente es una función simple que registra que se ha desencadenado y responde con Hello, world!:

const { app } = require('@azure/functions');

app.http('helloWorld1', {
    methods: ['POST', 'GET'],
    handler: async (request, context) => {
        context.log('Http function was triggered.');
        return { body: 'Hello, world!' };
    }
});

Entradas y salidas

La función es necesaria para tener exactamente una entrada principal denominada desencadenador. También puede tener entradas o salidas secundarias. Las entradas y salidas se configuran en los archivos function.json y también se conocen como enlaces.

Entradas

Las entradas son enlaces con direction establecido en in. La principal diferencia entre un desencadenador y una entrada secundaria es que type para un desencadenador finaliza en Trigger; por ejemplo, el tipo blobTrigger en comparación con el tipo blob. La mayoría de las funciones solo usan un desencadenador y no se admiten muchos tipos de entrada secundarios.

Se puede acceder a las entradas de varias maneras:

  • [Recomendado] Como argumentos pasados a la función: use los argumentos en el mismo orden en que se definen en function.json. La propiedad name definida en function.json no tiene que coincidir con el nombre del argumento, aunque se recomienda para la organización.

    module.exports = async function (context, myTrigger, myInput, myOtherInput) { ... };
    
  • Como propiedades de context.bindings: use la clave que coincide con la propiedad name definida en function.json.

    module.exports = async function (context) {
        context.log("This is myTrigger: " + context.bindings.myTrigger);
        context.log("This is myInput: " + context.bindings.myInput);
        context.log("This is myOtherInput: " + context.bindings.myOtherInput);
    };
    

Salidas

Las salidas son enlaces con direction establecido en out y se pueden establecer de varias maneras:

  • [Recomendado para una salida única] Devuelva el valor directamente: si usa una función asincrónica, puede devolver el valor directamente. Debe cambiar la propiedad name del enlace de salida a $return en function.json como en el ejemplo siguiente:

    {
        "name": "$return",
        "type": "http",
        "direction": "out"
    }
    
    module.exports = async function (context, request) {
        return {
            body: "Hello, world!"
        };
    }
    
  • [Recomendado para varias salidas] Devuelva un objeto que contenga todas las salidas: si usa una función asincrónica, puede devolver un objeto con una propiedad que coincida con el nombre de cada enlace de function.json. En el ejemplo siguiente se usan enlaces de salida denominados "httpResponse" y "queueOutput":

    {
        "name": "httpResponse",
        "type": "http",
        "direction": "out"
    },
    {
        "name": "queueOutput",
        "type": "queue",
        "direction": "out",
        "queueName": "helloworldqueue",
        "connection": "storage_APPSETTING"
    }
    
    module.exports = async function (context, request) {
        let message = 'Hello, world!';
        return {
            httpResponse: {
                body: message
            },
            queueOutput: message
        };
    };
    
  • Establezca valores en context.bindings: si no usa una función asincrónica o no quiere usar las opciones anteriores, puede establecer los valores directamente en context.bindings, en los que la clave coincide con el nombre del enlace. En el ejemplo siguiente se usan enlaces de salida denominados "httpResponse" y "queueOutput":

    {
        "name": "httpResponse",
        "type": "http",
        "direction": "out"
    },
    {
        "name": "queueOutput",
        "type": "queue",
        "direction": "out",
        "queueName": "helloworldqueue",
        "connection": "storage_APPSETTING"
    }
    
    module.exports = async function (context, request) {
        let message = 'Hello, world!';
        context.bindings.httpResponse = {
            body: message
        };
        context.bindings.queueOutput = message;
    };
    

Tipo de datos de enlaces

Puede usar la propiedad dataType en un enlace de entrada para cambiar el tipo de entrada, pero con algunas limitaciones:

  • En Node.js, solo se admiten string y binary (stream no).
  • En el caso de las entradas HTTP, se omite la propiedad dataType. En su lugar, use propiedades en el objeto request para obtener el cuerpo en el formato que quiera. Para obtener más información, consulte Solicitud HTTP.

En el ejemplo siguiente de un desencadenador de cola de almacenamiento, el tipo predeterminado de myQueueItem es string, pero si se establece dataType en binary, el tipo cambia a Buffer de Node.js.

{
    "name": "myQueueItem",
    "type": "queueTrigger",
    "direction": "in",
    "queueName": "helloworldqueue",
    "connection": "storage_APPSETTING",
    "dataType": "binary"
}
const { Buffer } = require('node:buffer');

module.exports = async function (context, myQueueItem) {
    if (typeof myQueueItem === 'string') {
        context.log('myQueueItem is a string');
    } else if (Buffer.isBuffer(myQueueItem)) {
        context.log('myQueueItem is a buffer');
    }
};

La función es necesaria para tener exactamente una entrada principal denominada desencadenador. También puede tener entradas secundarias, una salida principal denominada salida de retorno o salidas secundarias. Las entradas y salidas también se conocen como enlaces fuera del contexto del modelo de programación de Node.js. Antes de la versión v4 del modelo, estos enlaces se configuraron en archivos function.json.

Entrada del desencadenador

El desencadenador es la única entrada o salida necesaria. Para la mayoría de los tipos de desencadenador, se registra una función mediante un método en el objeto app denominado después del tipo de desencadenador. Puede especificar la configuración específica del desencadenador directamente en el argumento options. Por ejemplo, un desencadenador HTTP permite especificar una ruta. Durante la ejecución, el valor correspondiente a este desencadenador se pasa como primer argumento al controlador.

const { app } = require('@azure/functions');

app.http('helloWorld1', {
    route: 'hello/world',
    handler: async (request, context) => {
        ...
    }
});

Salida devuelta

La salida de devolución es opcional y, en algunos casos, está configurada de forma predeterminada. Por ejemplo, un desencadenador HTTP registrado con app.http está configurado para devolver automáticamente una salida de respuesta HTTP. Para la mayoría de los tipos de salida, especifique la configuración de devolución en el argumento options con la ayuda del objeto output exportado desde el módulo @azure/functions. Durante la ejecución, esta salida se establece devolviendo desde el controlador.

En el ejemplo siguiente se usa un desencadenador de temporizador y una salida de cola de almacenamiento:

const { app, output } = require('@azure/functions');

app.timer('timerTrigger1', {
    schedule: '0 */5 * * * *',
    return: output.storageQueue({
        connection: 'storage_APPSETTING',
        ...
    }),
    handler: (myTimer, context) => {
        return { hello: 'world' }
    }
});

Entradas y salidas adicionales

Además del desencadenador y la devolución, es posible especificar entradas o salidas adicionales en el argumento options al registrar una función. Los objetos input y output exportados desde el módulo @azure/functions proporcionan métodos específicos del tipo para ayudar a construir la configuración. Durante la ejecución, obtiene o establece los valores con context.extraInputs.get o context.extraOutputs.set, pasando el objeto de configuración original como primer argumento.

El ejemplo siguiente es una función desencadenada por una cola de almacenamiento, con una entrada de blob de almacenamiento adicional que se copia en una salida de blob adicional. El mensaje de cola debe ser el nombre de un archivo y reemplaza {queueTrigger} como nombre de blob que se va a copiar, con la ayuda de una expresión de enlace.

const { app, input, output } = require('@azure/functions');

const blobInput = input.storageBlob({
    connection: 'storage_APPSETTING',
    path: 'helloworld/{queueTrigger}',
});

const blobOutput = output.storageBlob({
    connection: 'storage_APPSETTING',
    path: 'helloworld/{queueTrigger}-copy',
});

app.storageQueue('copyBlob1', {
    queueName: 'copyblobqueue',
    connection: 'storage_APPSETTING',
    extraInputs: [blobInput],
    extraOutputs: [blobOutput],
    handler: (queueItem, context) => {
        const blobInputValue = context.extraInputs.get(blobInput);
        context.extraOutputs.set(blobOutput, blobInputValue);
    }
});

Entradas y salidas genéricas

Los objetos app, trigger, input y output exportados por el módulo @azure/functions proporcionan métodos específicos del tipo para la mayoría de los tipos. Para todos los tipos que no se admiten, se proporciona un método generic para que pueda especificar manualmente la configuración. El método generic también se puede usar si desea cambiar la configuración predeterminada proporcionada por un método específico del tipo.

El ejemplo siguiente es una función simple desencadenada por HTTP mediante métodos genéricos en lugar de métodos específicos del tipo.

const { app, output, trigger } = require('@azure/functions');

app.generic('helloWorld1', {
    trigger: trigger.generic({
        type: 'httpTrigger',
        methods: ['GET', 'POST']
    }),
    return: output.generic({
        type: 'http'
    }),
    handler: async (request, context) => {
        context.log(`Http function processed request for url "${request.url}"`);

        return { body: `Hello, world!` };
    }
});

Contexto de invocación

Cada invocación de la función recibe un objeto de invocación context, que se usa para leer entradas, establecer salidas, escribir en registros y leer varios metadatos. En el modelo v3, el objeto de contexto siempre es el primer argumento pasado al controlador.

El objeto context tiene las siguientes propiedades:

Propiedad Descripción
invocationId El identificador de la invocación de la función actual.
executionContext Consulte Contexto de ejecución.
bindings Consulte Enlaces.
bindingData Metadatos sobre la entrada del desencadenador para esta invocación, sin incluir el propio valor. Por ejemplo, un desencadenador del centro de eventos tiene una propiedad enqueuedTimeUtc.
traceContext Contexto para el seguimiento distribuido. Para obtener más información, vea Trace Context.
bindingDefinitions Configuración de las entradas y salidas, tal como se define en function.json.
req Consulte Solicitud HTTP.
res Consulte Respuesta HTTP.

context.executionContext

El objeto context.executionContext tiene las siguientes propiedades:

Propiedad Descripción
invocationId El identificador de la invocación de la función actual.
functionName Nombre de la función invocada. EL nombre de la carpeta que contiene el archivo function.json determina el nombre de la función.
functionDirectory Carpeta que contiene el archivo function.json.
retryContext Consulte Contexto de reintento.

context.executionContext.retryContext

El objeto context.executionContext.retryContext tiene las siguientes propiedades:

Propiedad Descripción
retryCount Número que representa el intento de reintento actual.
maxRetryCount Número máximo de veces que se reintenta una ejecución. Un valor de -1 significa que se reintentará indefinidamente.
exception Excepción que provocó el reintento.

context.bindings

El objeto context.bindings se usa para leer entradas o establecer salidas. El ejemplo siguiente es un desencadenador de cola de almacenamiento, que usa context.bindings para copiar una entrada de blob de almacenamiento a una salida de blob de almacenamiento. El contenido del mensaje de la cola reemplaza {queueTrigger} como nombre de archivo que se va a copiar, con la ayuda de una expresión de enlace.

{
    "name": "myQueueItem",
    "type": "queueTrigger",
    "direction": "in",
    "connection": "storage_APPSETTING",
    "queueName": "helloworldqueue"
},
{
    "name": "myInput",
    "type": "blob",
    "direction": "in",
    "connection": "storage_APPSETTING",
    "path": "helloworld/{queueTrigger}"
},
{
    "name": "myOutput",
    "type": "blob",
    "direction": "out",
    "connection": "storage_APPSETTING",
    "path": "helloworld/{queueTrigger}-copy"
}
module.exports = async function (context, myQueueItem) {
    const blobValue = context.bindings.myInput;
    context.bindings.myOutput = blobValue;
};

context.done

Este método context.done está en desuso. Antes de que se admitieran las funciones asincrónicas, se indicaba que la función había terminado mediante una llamada a context.done():

module.exports = function (context, request) {
    context.log("this pattern is now deprecated");
    context.done();
};

Ahora, se recomienda quitar la llamada a context.done() y marcar la función como asincrónica para que devuelva una promesa (incluso si no aplica await a nada). Tan pronto como finalice la función (en otras palabras, se resuelva la promesa devuelta), el modelo v3 sabrá que la función ha terminado.

module.exports = async function (context, request) {
    context.log("you don't need context.done or an awaited call")
};

Cada invocación de la función recibe un objeto context de invocación, con información adicional sobre el contexto y los métodos usados para el registro. En el modelo v4, el objeto context suele ser el segundo argumento pasado al controlador.

La clase InvocationContext tiene las siguientes propiedades:

Propiedad Descripción
invocationId El identificador de la invocación de la función actual.
functionName El nombre de la función.
extraInputs Se usa para obtener los valores de entradas adicionales. Para obtener más información, consulte Entradas y salidas adicionales.
extraOutputs Se usa para establecer los valores de salidas adicionales. Para obtener más información, consulte Entradas y salidas adicionales.
retryContext Consulte Contexto de reintento.
traceContext Contexto para el seguimiento distribuido. Para obtener más información, vea Trace Context.
triggerMetadata Metadatos sobre la entrada del desencadenador para esta invocación, sin incluir el propio valor. Por ejemplo, un desencadenador del centro de eventos tiene una propiedad enqueuedTimeUtc.
options Las opciones usadas al registrar la función, después de que se hayan validado y con los valores predeterminados especificados explícitamente.

Contexto de reintento

El objeto retryContext tiene las siguientes propiedades:

Propiedad Descripción
retryCount Número que representa el intento de reintento actual.
maxRetryCount Número máximo de veces que se reintenta una ejecución. Un valor de -1 significa que se reintentará indefinidamente.
exception Excepción que provocó el reintento.

Para obtener más información, vea retry-policies.

Registro

En Azure Functions, se recomienda usar context.log() para escribir registros. Azure Functions se integra con Azure Application Insights para capturar mejor los registros de la aplicación de funciones. Application Insights, que forma parte de Azure Monitor, proporciona funciones para la recopilación, la representación visual y el análisis de los datos de registro de la aplicación y de las salidas del seguimiento. Para más información, consulte Supervisión de Azure Functions.

Nota:

Si usa el método alternativo console.log de Node.js, se realiza un seguimiento de esos registros en el nivel de aplicación y no se asociará a ninguna función específica. Se recomienda encarecidamente usar context para el registro, en lugar de console, para que todos los registros estén asociados a una función específica.

En el ejemplo siguiente se escribe un registro en el nivel de "información" predeterminado, incluido el id. de invocación:

context.log(`Something has happened. Invocation ID: "${context.invocationId}"`);

Niveles de registro

Además del método context.log predeterminado, están disponibles los siguientes métodos para permitirle escribir registros en niveles específicos:

Método Descripción
context.log.error() Escribe un evento de nivel de error en los registros.
context.log.warn() Escribe un evento de nivel de advertencia en los registros.
context.log.info() Escribe un evento de nivel de información en los registros.
context.log.verbose() Escribe un evento de nivel de seguimiento en los registros.
Método Descripción
context.trace() Escribe un evento de nivel de seguimiento en los registros.
context.debug() Escribe un evento de nivel de depuración en los registros.
context.info() Escribe un evento de nivel de información en los registros.
context.warn() Escribe un evento de nivel de advertencia en los registros.
context.error() Escribe un evento de nivel de error en los registros.

Configurar el nivel de registro

Azure Functions permite definir el nivel de umbral que se usará al seguir y visualizar los registros. Para establecer el umbral, use la propiedad logging.logLevel del archivo host.json. Esta propiedad permite definir un nivel predeterminado aplicado a todas las funciones o un umbral para cada función individual. Para más información, consulte Configuración de la supervisión para Azure Functions.

Seguimiento de datos personalizados

De forma predeterminada, Azure Functions escribe las salidas como seguimientos en Application Insights. Para obtener más control, puede usar en su lugar el SDK de Node.js para Application Insights para enviar los datos personalizados a la instancia de Application Insights.

const appInsights = require("applicationinsights");
appInsights.setup();
const client = appInsights.defaultClient;

module.exports = async function (context, request) {
    // Use this with 'tagOverrides' to correlate custom logs to the parent function invocation.
    var operationIdOverride = {"ai.operation.id":context.traceContext.traceparent};

    client.trackEvent({name: "my custom event", tagOverrides:operationIdOverride, properties: {customProperty2: "custom property value"}});
    client.trackException({exception: new Error("handled exceptions can be logged with this method"), tagOverrides:operationIdOverride});
    client.trackMetric({name: "custom metric", value: 3, tagOverrides:operationIdOverride});
    client.trackTrace({message: "trace message", tagOverrides:operationIdOverride});
    client.trackDependency({target:"http://dbname", name:"select customers proc", data:"SELECT * FROM Customers", duration:231, resultCode:0, success: true, dependencyTypeName: "ZSQL", tagOverrides:operationIdOverride});
    client.trackRequest({name:"GET /customers", url:"http://myserver/customers", duration:309, resultCode:200, success:true, tagOverrides:operationIdOverride});
};

El parámetro tagOverrides establece operation_Id en el identificador de invocación de la función. Esta configuración le permite correlacionar todos los registros personalizados y generados automáticamente para una determinada invocación de función.

Desencadenadores HTTP

Los desencadenadores HTTP y de webhook usan objetos de solicitud y respuesta para representar mensajes HTTP.

Los desencadenadores HTTP y de webhook usan objetos HttpRequest y HttpResponse para representar mensajes HTTP. Las clases representan un subconjunto del estándar de captura, mediante el paquete undici de Node.js.

Solicitud HTTP

Se puede acceder a una solicitud de varias maneras:

  • Como segundo argumento de la función:

    module.exports = async function (context, request) {
        context.log(`Http function processed request for url "${request.url}"`);
    
  • Desde la propiedad context.req:

    module.exports = async function (context, request) {
        context.log(`Http function processed request for url "${context.req.url}"`);
    
  • Desde los enlaces de entrada con nombre: esta opción funciona igual que cualquier enlace no HTTP. El nombre del enlace de function.json debe coincidir con la clave en context.bindings, o "request1" en el ejemplo siguiente:

    {
        "name": "request1",
        "type": "httpTrigger",
        "direction": "in",
        "authLevel": "anonymous",
        "methods": [
            "get",
            "post"
        ]
    }
    
    module.exports = async function (context, request) {
        context.log(`Http function processed request for url "${context.bindings.request1.url}"`);
    

El objeto HttpRequest tiene las siguientes propiedades:

Propiedad Tipo Descripción
method string Método de solicitud HTTP usado para invocar esta función.
url string URL de la solicitud.
headers Record<string, string> Encabezados de solicitud HTTP. Este objeto distingue entre mayúsculas y minúsculas. Se recomienda usar request.getHeader('header-name') en su lugar, que no distingue entre mayúsculas y minúsculas.
query Record<string, string> Claves y valores de los parámetros de la cadena de consulta de la URL.
params Record<string, string> Valores y claves de parámetros de ruta.
user HttpRequestUser | null Objeto que representa al usuario que ha iniciado sesión, ya sea a través de la autenticación de Functions, la autenticación SWA o null cuando no se inicia sesión ningún usuario de este tipo.
body Buffer | string | any Si el tipo de medio es "application/octet-stream" o "multipart/*", body es un búfer. Si el valor es una cadena que puede analizar como JSON, body es el objeto analizado. De lo contrario, body es una cadena.
rawBody string Cuerpo como cadena. A pesar del nombre, esta propiedad no devuelve un búfer.
bufferBody Buffer Cuerpo como búfer.

Se puede acceder a la solicitud como primer argumento para el controlador de una función desencadenada por HTTP.

async (request, context) => {
    context.log(`Http function processed request for url "${request.url}"`);

El objeto HttpRequest tiene las siguientes propiedades:

Propiedad Tipo Descripción
method string Método de solicitud HTTP usado para invocar esta función.
url string URL de la solicitud.
headers Headers Encabezados de solicitud HTTP.
query URLSearchParams Claves y valores de los parámetros de la cadena de consulta de la URL.
params Record<string, string> Valores y claves de parámetros de ruta.
user HttpRequestUser | null Objeto que representa al usuario que ha iniciado sesión, ya sea a través de la autenticación de Functions, la autenticación SWA o null cuando no se inicia sesión ningún usuario de este tipo.
body ReadableStream | null Cuerpo como una secuencia legible.
bodyUsed boolean Valor booleano que indica si el cuerpo ya se ha leído.

Para acceder al cuerpo de una solicitud o respuesta, se pueden usar los métodos siguientes:

Método Tipo de valor devuelto
arrayBuffer() Promise<ArrayBuffer>
blob() Promise<Blob>
formData() Promise<FormData>
json() Promise<unknown>
text() Promise<string>

Nota:

Las funciones del cuerpo solo se pueden ejecutar una vez; Las llamadas posteriores se resolverán con cadenas vacías o ArrayBuffers.

Respuesta HTTP

La respuesta se puede establecer de varias maneras:

  • Establezca la propiedad context.res:

    module.exports = async function (context, request) {
        context.res = { body: `Hello, world!` };
    
  • Devuelva la respuesta: si la función es asincrónica y establece el nombre de enlace en $return en function.json, puede devolver la respuesta directamente en lugar de establecerla en context.

    {
      "type": "http",
      "direction": "out",
      "name": "$return"
    }
    
    module.exports = async function (context, request) {
        return { body: `Hello, world!` };
    
  • Establezca el enlace de entrada con nombre: esta opción funciona igual que cualquier enlace no HTTP. El nombre del enlace de function.json debe coincidir con la clave en context.bindings, o "response1" en el ejemplo siguiente:

    {
        "type": "http",
        "direction": "out",
        "name": "response1"
    }
    
    module.exports = async function (context, request) {
        context.bindings.response1 = { body: `Hello, world!` };
    
  • Llame a context.res.send(): esta opción está en desuso. Llama implícitamente a context.done() y no se puede usar en una función asincrónica.

    module.exports = function (context, request) {
        context.res.send(`Hello, world!`);
    

Si crea un objeto nuevo al establecer la respuesta, dicho objeto debe coincidir con la interfaz HttpResponseSimple, que tiene las siguientes propiedades:

Propiedad Tipo Descripción
headers Record<string, string> (opcional) Encabezados de respuesta HTTP.
cookies Cookie[] (opcional) Cookies de respuesta HTTP.
body any (opcional) Cuerpo de respuesta HTTP.
statusCode number (opcional) Código de estado de respuesta HTTP. Si no se establece, el valor predeterminado es 200.
status number (opcional) Es igual que statusCode. Esta propiedad se ignora si statusCode está establecido.

También puede modificar el objeto context.res sin sobrescribirlo. El objeto predeterminado context.res usa la interfaz HttpResponseFull, que admite los métodos siguientes además de las propiedades HttpResponseSimple:

Método Descripción
status() Establece el estado.
setHeader() Establece un campo de encabezado. NOTA: res.set() y res.header() también se admiten y hacen lo mismo.
getHeader() Obtiene un campo de encabezado. NOTA: res.get() también se admite y hace lo mismo.
removeHeader() Quita un encabezado.
type() Establece el encabezado "content-type".
send() Este método es desusado. Establece el cuerpo y llama a context.done() para indicar que ha terminado una función sincrónica. NOTA: res.end() también se admite y hace lo mismo.
sendStatus() Este método es desusado. Establece el código de estado y llama a context.done() para indicar que ha terminado una función sincrónica.
json() Este método es desusado. Establece el "content-type" en "application/json", establece el cuerpo y llama a context.done() para indicar que ha terminado una función sincrónica.

La respuesta se puede establecer de varias maneras:

  • Como interfaz sencilla con el tipo HttpResponseInit: esta opción es la forma más concisa de devolver respuestas.

    return { body: `Hello, world!` };
    

    La interfaz HttpResponseInit tiene las propiedades siguientes:

    Propiedad Tipo Descripción
    body BodyInit (opcional) Uno de los cuerpos de respuesta HTTP siguientes: ArrayBuffer, AsyncIterable<Uint8Array>, Blob, FormData, Iterable<Uint8Array>, NodeJS.ArrayBufferView, URLSearchParams, null o string.
    jsonBody any (opcional) Un cuerpo de respuesta HTTP serializable en JSON. Si se establece, la propiedad HttpResponseInit.body se omite en favor de esta propiedad.
    status number (opcional) Código de estado de respuesta HTTP. Si no se establece, el valor predeterminado es 200.
    headers HeadersInit (opcional) Encabezados de respuesta HTTP.
    cookies Cookie[] (opcional) Cookies de respuesta HTTP.
  • Como clase con tipo HttpResponse: esta opción proporciona métodos auxiliares para leer y modificar varias partes de la respuesta, como los encabezados.

    const response = new HttpResponse({ body: `Hello, world!` });
    response.headers.set('content-type', 'application/json');
    return response;
    

    La clase HttpResponse acepta un opcional HttpResponseInit como argumento para su constructor y tiene las siguientes propiedades:

    Propiedad Tipo Descripción
    status number Código de estado de respuesta HTTP.
    headers Headers Encabezados de respuesta HTTP.
    cookies Cookie[] Cookies de respuesta HTTP.
    body ReadableStream | null Cuerpo como una secuencia legible.
    bodyUsed boolean Valor booleano que indica si el cuerpo ya se ha leído.

Flujos HTTP (versión preliminar)

Los flujos HTTP son una característica que facilita el procesamiento de datos grandes, transmitir respuestas de OpenAI, ofrecer contenido dinámico y admitir otros escenarios HTTP principales. Permite transmitir solicitudes y respuestas desde puntos de conexión HTTP en la aplicación de funciones de Node.js. Use flujos HTTP en escenarios en los que la aplicación requiera intercambio en tiempo real e interacción entre el cliente y el servidor a través de HTTP. También puede usar secuencias HTTP para obtener el mejor rendimiento y confiabilidad para las aplicaciones cuando se usa HTTP.

Las secuencias HTTP se encuentran actualmente en versión preliminar.

Importante

Las secuencias HTTP no se admiten en el modelo v3. Actualice al modelo v4 para usar la característica de streaming HTTP.

Los tipos HttpRequest y HttpResponse existentes del modelo de programación v4 ya admiten varias formas de controlar el cuerpo del mensaje, incluso como una secuencia.

Requisitos previos

Habilitación de secuencias

Siga estos pasos para habilitar flujos HTTP en la aplicación de funciones en Azure y en los proyectos locales:

  1. Si planea transmitir grandes cantidades de datos, modifique la configuración FUNCTIONS_REQUEST_BODY_SIZE_LIMIT en Azure. El tamaño máximo de cuerpo predeterminado permitido es 104857600, que limita las solicitudes a un tamaño de ~100 MB.

  2. Para el desarrollo local, agregue también FUNCTIONS_REQUEST_BODY_SIZE_LIMIT al archivo local.settings.json.

  3. Agregue el código siguiente a la aplicación en cualquier archivo incluido en el campo principal.

    const { app } = require('@azure/functions'); 
    
    app.setup({ enableHttpStream: true });
    

Ejemplos de secuencias

En este ejemplo se muestra una función desencadenada por HTTP que recibe datos a través de una solicitud HTTP POST y la función transmite estos datos a un archivo de salida especificado:

const { app } = require('@azure/functions');
const { createWriteStream } = require('fs');
const { Writable } = require('stream');

app.http('httpTriggerStreamRequest', {
    methods: ['POST'],
    authLevel: 'anonymous',
    handler: async (request, context) => {
        const writeStream = createWriteStream('<output file path>');
        await request.body.pipeTo(Writable.toWeb(writeStream));

        return { body: 'Done!' };
    },
});

En este ejemplo se muestra una función desencadenada por HTTP que transmite el contenido de un archivo como respuesta a las solicitudes HTTP GET entrantes:

const { app } = require('@azure/functions');
const { createReadStream } = require('fs');

app.http('httpTriggerStreamResponse', {
    methods: ['GET'],
    authLevel: 'anonymous',
    handler: async (request, context) => {
        const body = createReadStream('<input file path>');

        return { body };
    },
});

Consideraciones sobre secuencias

  • El objeto request.params no se admite al usar secuencias HTTP durante la versión preliminar. Consulte este problema de GitHub para obtener más información y soluciones alternativas sugeridas.

  • Use request.body para obtener la ventaja máxima del uso de secuencias. Todavía puede seguir usando métodos como request.text(), que siempre devuelven el cuerpo como una cadena.

Enlaces

Los enlaces no se admiten en el modelo v3. Actualice al modelo v4 para usar enlaces.

Use un enlace para ejecutar código en distintos puntos del ciclo de vida de Azure Functions. Los enlaces se ejecutan en el orden en que están registrados y se pueden registrar desde cualquier archivo de la aplicación. Actualmente hay dos ámbitos de enlaces, nivel "aplicación" e "invocación".

Enlaces de invocación

Los enlaces de invocación se ejecutan una vez por cada invocación de su función, ya sea antes en un enlace preInvocation o después en un enlace postInvocation. De manera predeterminada, su enlace se ejecuta para todos los tipos de desencadenador, pero también puede filtrar por tipo. En el ejemplo siguiente se muestra cómo registrar un enlace de invocación y filtrar por tipo de desencadenador:

const { app } = require('@azure/functions');

app.hook.preInvocation((context) => {
    if (context.invocationContext.options.trigger.type === 'httpTrigger') {
        context.invocationContext.log(
            `preInvocation hook executed for http function ${context.invocationContext.functionName}`
        );
    }
});

app.hook.postInvocation((context) => {
    if (context.invocationContext.options.trigger.type === 'httpTrigger') {
        context.invocationContext.log(
            `postInvocation hook executed for http function ${context.invocationContext.functionName}`
        );
    }
});

El primer argumento para el controlador de enlace es un objeto de contexto específico de ese tipo de enlace.

El objeto PreInvocationContext tiene las siguientes propiedades:

Propiedad Descripción
inputs Argumentos pasados a la invocación.
functionHandler Controlador de función para la invocación. Los cambios en este valor afectan a la propia función.
invocationContext Objeto de contexto de invocación que se pasa a la función.
hookData Lugar recomendado para almacenar y compartir datos entre enlaces en el mismo ámbito. Debe usar un nombre de propiedad único para que no entre en conflicto con los datos de otros enlaces.

El objeto PostInvocationContext tiene las siguientes propiedades:

Propiedad Descripción
inputs Argumentos pasados a la invocación.
result Resultado de la función. Los cambios en este valor afectan al resultado general de la función.
error Error lanzado por la función, o null/indefinido si no hay error. Los cambios en este valor afectan al resultado general de la función.
invocationContext Objeto de contexto de invocación que se pasa a la función.
hookData Lugar recomendado para almacenar y compartir datos entre enlaces en el mismo ámbito. Debe usar un nombre de propiedad único para que no entre en conflicto con los datos de otros enlaces.

Enlaces de aplicación

Los enlaces de aplicación se ejecutan una vez por cada instancia de su aplicación, ya sea durante el inicio en un enlace de appStart o durante la finalización en un enlace de appTerminate. Los enlaces de finalización de aplicación tienen un tiempo limitado para ejecutarse y no se ejecutan en todos los escenarios.

El entorno de ejecución de Azure Functions actualmente no admite registro de contexto fuera de una invocación. Use el paquete npm de Application Insights para registrar los datos durante los enlaces a nivel de aplicación.

En el ejemplo siguiente se registran enlaces de aplicación:

const { app } = require('@azure/functions');

app.hook.appStart((context) => {
    // add your logic here
});

app.hook.appTerminate((context) => {
    // add your logic here
});

El primer argumento para el controlador de enlace es un objeto de contexto específico de ese tipo de enlace.

El objeto AppStartContext tiene las siguientes propiedades:

Propiedad Descripción
hookData Lugar recomendado para almacenar y compartir datos entre enlaces en el mismo ámbito. Debe usar un nombre de propiedad único para que no entre en conflicto con los datos de otros enlaces.

El objeto AppTerminateContext tiene las siguientes propiedades:

Propiedad Descripción
hookData Lugar recomendado para almacenar y compartir datos entre enlaces en el mismo ámbito. Debe usar un nombre de propiedad único para que no entre en conflicto con los datos de otros enlaces.

Escalado y simultaneidad

De forma predeterminada, Azure Functions supervisa automáticamente la carga de su aplicación y crea más instancias de host para Node.js según sea necesario. Azure Functions usa umbrales integrados (no configurables por el usuario) en diferentes tipos de desencadenadores para decidir cuándo se deberán agregar instancias, como la antigüedad de los mensajes y el tamaño de la cola de QueueTrigger. Para obtener más información, consulte Cómo funcionan los planes de consumo y Premium.

Este comportamiento de escalado es suficiente para muchas aplicaciones de Node.js. En las aplicaciones dependientes de la CPU, puede mejorar aún más el rendimiento mediante el uso de varios procesos de trabajo de lenguaje. Puede aumentar el número de procesos de trabajo por host del valor predeterminado de 1 a un máximo de 10 mediante la configuración de la aplicación FUNCTIONS_WORKER_PROCESS_COUNT. Al hacerlo, Azure Functions intenta distribuir uniformemente las invocaciones de función simultáneas en estos trabajos. Este comportamiento hará que sea menos probable que una función que consuma mucha CPU bloquee la ejecución de otras funciones. La configuración se aplica a cada host que Azure Functions crea al escalar horizontalmente la aplicación para satisfacer la demanda.

Advertencia

Use la configuración FUNCTIONS_WORKER_PROCESS_COUNT con precaución. Ejecutar varios procesos en la misma instancia puede provocar un comportamiento impredecible y aumentar los tiempos de carga de la función. Si usa esta configuración, se recomienda encarecidamente compensar estas desventajas mediante la ejecución desde un archivo de paquete.

Versión de Node

Puede ver la versión actual que el entorno de tiempo de ejecución usa mediante el registro de process.version desde cualquier función. Consulte supported versions para obtener una lista de Node.js versiones compatibles con cada modelo de programación.

Especificación de la versión de Node

La forma en que actualice la versión de Node.js depende del sistema operativo en el que se ejecuta la aplicación de funciones.

Cuando se ejecuta en Windows, la versión de Node.js se establece mediante la configuración de la aplicación WEBSITE_NODE_DEFAULT_VERSION. Esta configuración se puede actualizar mediante la CLI de Azure o en el Azure Portal.

Para saber más sobre las versiones de Node.js, vea las versiones compatibles.

Antes de actualizar su versión de Node.js, asegúrese de que la aplicación de funciones se ejecuta en la versión más reciente del entorno de ejecución de Azure Functions. Si necesita actualizar su versión del entorno de ejecución, consulte Migración de las aplicaciones de la versión 3.x de Azure Functions a la versión 4.x.

Ejecute el comando az functionapp config appsettings set de la CLI de Azure para actualizar su versión de Node.js de la aplicación de funciones que se ejecuta en Windows:

az functionapp config appsettings set  --settings WEBSITE_NODE_DEFAULT_VERSION=~20 \
 --name <FUNCTION_APP_NAME> --resource-group <RESOURCE_GROUP_NAME> 

Esto establece la configuración de la WEBSITE_NODE_DEFAULT_VERSION aplicación de la versión de LTS admitida de ~20.

Una vez realizados los cambios, la aplicación de funciones se reinicia. Para saber más sobre la compatibilidad de Node.js con Functions, consulte Directiva de compatibilidad de Language Runtime.

Variables de entorno

Las variables de entorno pueden ser útiles para los secretos operativos (cadenas de conexión, claves, puntos de conexión, etc.) o para la configuración del entorno, como las variables de generación de perfiles. Puede agregar variables de entorno en los entornos locales y en la nube y acceder a ellas a través de process.env en el código de la función.

En el ejemplo siguiente se registra la variable de entorno WEBSITE_SITE_NAME:

module.exports = async function (context) {
    context.log(`WEBSITE_SITE_NAME: ${process.env["WEBSITE_SITE_NAME"]}`);
}
async function timerTrigger1(myTimer, context) {
    context.log(`WEBSITE_SITE_NAME: ${process.env["WEBSITE_SITE_NAME"]}`);
}

Entorno de desarrollo local

Cuando se ejecute a nivel local, el proyecto de funciones incluirá un local.settings.json archivo, donde se almacenarán las variables de entorno en el objeto Values.

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "",
    "FUNCTIONS_WORKER_RUNTIME": "node",
    "CUSTOM_ENV_VAR_1": "hello",
    "CUSTOM_ENV_VAR_2": "world"
  }
}

Entorno en la nube de Azure

Cuando se ejecute en Azure, la aplicación de funciones permitirá establecer usos de la configuración de la aplicación, como cadenas de conexión de servicio, y expondrá estos valores como variables de entorno durante la ejecución.

Hay varias maneras de agregar, actualizar y eliminar opciones de configuración de la aplicación de función:

Para aplicar los cambios realizados en la configuración de la aplicación de funciones, es necesario reiniciar la aplicación de funciones.

Variables de entorno de trabajo

Hay varias variables de entorno de Functions específicas de Node.js:

languageWorkers__node__arguments

Esta configuración permite especificar argumentos personalizados al iniciar el proceso de Node.js. A menudo se usa localmente para iniciar el trabajo en modo de depuración, pero también se puede usar en Azure si necesita argumentos personalizados.

Advertencia

Si es posible, evite el uso de languageWorkers__node__arguments en Azure porque puede tener un efecto negativo en el tiempo de inicio en frío. En lugar de usar trabajos iniciados previamente, el tiempo de ejecución debe iniciar un nuevo trabajo desde cero con los argumentos personalizados.

logging__logLevel__Worker

Esta configuración ajusta el nivel de registro predeterminado para los registros de trabajo específicos de Node.js. De forma predeterminada, solo se muestran los registros de advertencias o errores, pero puede establecerla en information o debug para ayudar a diagnosticar problemas con el trabajo de Node.js. Para más información, consulte Configuración de los niveles de registro.

Módulos ECMAScript (versión preliminar)

Nota

Los módulos ECMAScript son actualmente una característica en vista previa en Node.js 14 y siguientes en Azure Functions.

Los módulos ECMAScript (módulos ES) son el nuevo sistema de módulos estándar oficial de Node.js. Hasta ahora, en los ejemplos de código de este artículo se usa la sintaxis CommonJS. Al ejecutar Azure Functions en Node.js 14 o una versión posterior, puede optar por escribir las funciones mediante la sintaxis de los módulos ES.

Para usar los módulos ES en una función, cambie su nombre de archivo para usar una extensión .mjs. El siguiente archivo index.mjs es una función desencadenada por HTTP que usa la sintaxis de los módulos ES para importar la biblioteca uuid y devolver un valor.

import { v4 as uuidv4 } from 'uuid';

async function httpTrigger1(context, request) {
    context.res.body = uuidv4();
};

export default httpTrigger;
import { v4 as uuidv4 } from 'uuid';

async function httpTrigger1(request, context) {
    return { body: uuidv4() };
};

app.http('httpTrigger1', {
    methods: ['GET', 'POST'],
    handler: httpTrigger1
});

Configuración del punto de entrada de la función

Las propiedades de function.jsonscriptFile y entryPoint pueden usarse para configurar la ubicación y el nombre de la función exportada. La propiedad scriptFile es necesaria cuando se usa TypeScript y se debe apuntar al JavaScript compilado.

Usar scriptFile

De forma predeterminada, se ejecuta una función de JavaScript desde index.js, un archivo que comparte el mismo directorio primario que su archivo function.json correspondiente.

scriptFile se puede usar para obtener una estructura de carpetas que tenga el aspecto del ejemplo siguiente:

<project_root>/
 | - node_modules/
 | - myFirstFunction/
 | | - function.json
 | - lib/
 | | - sayHello.js
 | - host.json
 | - package.json

function.json para myFirstFunction debe incluir una propiedad scriptFile que señala al archivo con la función exportada que ejecutar.

{
  "scriptFile": "../lib/sayHello.js",
  "bindings": [
    ...
  ]
}

Usar entryPoint

En el modelo v3, una función debe exportarse con module.exports para que se pueda encontrar y ejecutar. De forma predeterminada, la función que se ejecuta cuando se desencadena es la única exportación de ese archivo, la exportación denominada run o la exportación denominada index. El ejemplo siguiente establece entryPoint en function.json un valor personalizado, "logHello":

{
  "entryPoint": "logHello",
  "bindings": [
    ...
  ]
}
async function logHello(context) {
    context.log('Hello, world!');
}

module.exports = { logHello };

Depuración local

Se recomienda usar VS Code para la depuración local, que inicia automáticamente el proceso de Node.js en modo de depuración y se adjunta automáticamente al proceso. Para obtener más información, consulte Ejecución local de la función.

Si usa una herramienta diferente para la depuración o quiere iniciar manualmente el proceso de Node.js en modo de depuración, agregue "languageWorkers__node__arguments": "--inspect" después de Values en local.settings.json. El argumento --inspect indica a Node.js que escuche un cliente de depuración, en el puerto 9229 de forma predeterminada. Para obtener más información, consulte la guía de depuración de Node.js.

Recomendaciones

En esta sección se describen varios patrones impactantes para aplicaciones de Node.js que recomendamos seguir.

Elección de los planes de App Service de una sola vCPU

Al crear una aplicación de función que usa el plan de App Service, se recomienda que seleccione un plan de una sola vCPU, en lugar de un plan con varias vCPU. En la actualidad, Functions ejecuta funciones de Node.js con más eficacia en VM con una sola vCPU. El uso de máquinas virtuales más grandes no producirá las mejoras de rendimiento esperadas. Cuando sea necesario, puede escalar horizontalmente de forma manual mediante la adición de más instancias de máquina virtual de una sola vCPU o bien puede habilitar el escalado automático. Para obtener más información, consulte Escalación del recuento de instancias de forma manual o automática.

Ejecución desde un archivo de paquete

Al desarrollar Azure Functions en el modelo de hospedaje sin servidor, los arranques en frío son una realidad. Arranque en frío hace referencia a la primera vez en que su aplicación de funciones se inicia después de un período de inactividad, tardando más tiempo en iniciarse. Especialmente para las aplicaciones de Node.js con árboles de dependencias de gran tamaño, el arranque en frío puede ser importante. Para acelerar el proceso de arranque en frío, ejecute sus funciones como un archivo de paquete cuando sea posible. Muchos métodos de implementación usan este modelo de forma predeterminada, pero si experimentase numerosos arranques en frío, debería hacer alguna comprobación para asegurarse de que se esté ejecutando de esta manera.

Uso de un único cliente estático

Cuando use un cliente específico del servicio en una aplicación de Azure Functions, no cree un cliente con cada invocación de función, ya que puede alcanzar los límites de conexiones. En su lugar, cree un único cliente estático en el ámbito global. Para más información, consulte Administrar conexiones en Azure Functions.

Uso de async y await

Al escribir funciones de Azure en Node.js, deberá escribir el código mediante las palabras clave async y await. El hecho de escribir el código con async y await en lugar de usar devoluciones de llamada o .then y .catch con promesas ayuda a evitar dos problemas comunes:

  • El inicio de excepciones no detectadas que bloquean el proceso de Node.js, que puede afectar a la ejecución de otras funciones.
  • Comportamiento inesperado, como la ausencia de registros de context.log debido a llamadas asincrónicas que no se esperan correctamente.

En el ejemplo siguiente, se invoca el método asincrónico fs.readFile con una función de devolución de llamada error-first como segundo parámetro. Este código provoca los dos problemas mencionados anteriormente. Una excepción no detectada explícitamente en el ámbito correcto puede bloquear todo el proceso (problema 1). Devolver sin asegurarse de que la devolución de llamada ha finalizado significa que la respuesta http a veces tendrá un cuerpo vacío (incidencia n.º 2).

// DO NOT USE THIS CODE
const { app } = require('@azure/functions');
const fs = require('fs');

app.http('httpTriggerBadAsync', {
    methods: ['GET', 'POST'],
    authLevel: 'anonymous',
    handler: async (request, context) => {
        let fileData;
        fs.readFile('./helloWorld.txt', (err, data) => {
            if (err) {
                context.error(err);
                // BUG #1: This will result in an uncaught exception that crashes the entire process
                throw err;
            }
            fileData = data;
        });
        // BUG #2: fileData is not guaranteed to be set before the invocation ends
        return { body: fileData };
    },
});

En el ejemplo siguiente, se invoca el método asincrónico fs.readFile con una función de devolución de llamada error-first como segundo parámetro. Este código provoca los dos problemas mencionados anteriormente. Una excepción no detectada explícitamente en el ámbito correcto puede bloquear todo el proceso (problema 1). Llamar al método en desuso context.done() fuera del ámbito de la devolución de llamada puede indicar que la función ha terminado antes de que se lea el archivo (problema 2). En este ejemplo, una llamada a context.done() demasiado pronto provoca la ausencia de las entradas de registro que empiezan por Data from file:.

// NOT RECOMMENDED PATTERN
const fs = require('fs');

module.exports = function (context) {
    fs.readFile('./hello.txt', (err, data) => {
        if (err) {
            context.log.error('ERROR', err);
            // BUG #1: This will result in an uncaught exception that crashes the entire process
            throw err;
        }
        context.log(`Data from file: ${data}`);
        // context.done() should be called here
    });
    // BUG #2: Data is not guaranteed to be read before the Azure Function's invocation ends
    context.done();
}

Use las palabras clave async y await para ayudar a evitar ambos problemas. La mayoría de las API del ecosistema de Node.js se han convertido para admitir promesas de alguna forma. Por ejemplo, a partir de la versión 14, Node.js proporciona una API fs/promises para reemplazar la API de devolución de llamada fs.

En el ejemplo siguiente, las excepciones no controladas iniciadas durante la ejecución de la función solo devolverán un error para la invocación individual que generó una excepción. La palabra clave await significa que los pasos después de readFile solo se ejecutarán una vez finalizado.

// Recommended pattern
const { app } = require('@azure/functions');
const fs = require('fs/promises');

app.http('httpTriggerGoodAsync', {
    methods: ['GET', 'POST'],
    authLevel: 'anonymous',
    handler: async (request, context) => {
        try {
            const fileData = await fs.readFile('./helloWorld.txt');
            return { body: fileData };
        } catch (err) {
            context.error(err);
            // This rethrown exception will only fail the individual invocation, instead of crashing the whole process
            throw err;
        }
    },
});

Con async y await, tampoco es necesario llamar a la devolución de llamada context.done().

// Recommended pattern
const fs = require('fs/promises');

module.exports = async function (context) {
    let data;
    try {
        data = await fs.readFile('./hello.txt');
    } catch (err) {
        context.log.error('ERROR', err);
        // This rethrown exception will be handled by the Functions Runtime and will only fail the individual invocation
        throw err;
    }
    context.log(`Data from file: ${data}`);
}

Solución de problemas

Consulte la guía de solución de problemas de Node.js.

Pasos siguientes

Para obtener más información, consulte los siguientes recursos: