Azure Functions JavaScript 开发人员指南

本指南包含有关使用 JavaScript 编写 Azure Functions 的复杂性的信息。

JavaScript 函数是导出的 function,它将在触发时执行(触发器在 function.json 中配置)。 每个函数要传递的第一个参数是 context 对象,该对象用于接收和发送绑定数据、日志记录以及与运行时通信。

本文假定你已阅读 Azure Functions 开发人员参考。 此外,应该完成有关使用 Visual Studio Code门户创建第一个函数的 Functions 快速入门。

文件夹结构

JavaScript 项目所需的文件夹结构如下所示。 可更改此默认值。 有关详细信息,请参阅下面的 scriptFile 部分。

FunctionsProject
 | - MyFirstFunction
 | | - index.js
 | | - function.json
 | - MySecondFunction
 | | - index.js
 | | - function.json
 | - SharedCode
 | | - myFirstHelperFunction.js
 | | - mySecondHelperFunction.js
 | - node_modules
 | - host.json
 | - package.json
 | - extensions.csproj
 | - bin

项目的根目录中有共享的 host.json 文件,可用于配置函数应用。 每个函数都具有一个文件夹,其中包含其代码文件 (.js) 和绑定配置文件 (function.json)。 function.json 父目录的名称始终是函数的名称。

2.x 版 Functions 运行时中所需的绑定扩展在 extensions.csproj 文件中定义,实际库文件位于 bin 文件夹中。 本地开发时,必须注册绑定扩展。 在 Azure 门户中开发函数时,系统将为你完成此注册。

导出函数

必须通过 module.exports(或 exports)导出 JavaScript 函数。 导出的函数应是触发时执行的 JavaScript 函数。

默认情况下,Functions 运行时会在 index.js 中查找你的函数,其中,index.js 与其相应的 function.json 共享同一个父目录。 默认情况下,导出的函数应该是其文件中的唯一导出,或者名为 runindex 的导出。 若要配置文件位置和导出函数名称,请阅读下面的配置函数的入口点

在执行时,将为导出的函数传递一些参数。 采用的第一个参数始终是 context 对象。 如果函数是同步的(不返回 Promise),则必须传递 context 对象,因为需要调用 context.done 才能正常使用该函数。

// You should include context, other arguments are optional
module.exports = function(context, myTrigger, myInput, myOtherInput) {
    // function logic goes here :)
    context.done();
};

导出异步函数

在 Functions 运行时版本 2.x 中使用 async function 声明或普通 JavaScript Promise,无需显式调用 context.done 回调即可通知函数已完成。 导出的异步函数/Promise 完成时,函数将完成。 对于面向版本 1.x 运行时的函数,在代码完成执行后,仍必须调用 context.done

以下示例是一个简单的函数,用于记录其已被触发并立即完成执行。

module.exports = async function (context) {
    context.log('JavaScript trigger function processed a request.');
};

导出异步函数时,还可配置输出绑定,以使用 return 值。 如果只有一个输出绑定,则建议使用此值。

若要使用 return 分配输出,请在 function.json 中将 name 属性更改为 $return

{
  "type": "http",
  "direction": "out",
  "name": "$return"
}

在这种情况下,函数应如以下示例所示:

module.exports = async function (context, req) {
    context.log('JavaScript HTTP trigger function processed a request.');
    // You can call and await an async method here
    return {
        body: "Hello, world!"
    };
}

绑定

在 JavaScript 中,需在函数的 function.json 中配置和定义绑定。 函数通过多种方式来与绑定交互。

输入

在 Azure Functions 中,输入分为两种类别:一种是触发器输入,另一种则是附加输入。 函数可通过三种方式读取触发器和其他输入绑定(direction === "in" 的绑定):

  • [建议] 以传递给函数的参数的形式。 它们以与 function.json 中定义的顺序相同的顺序传递给函数。 请注意,function.json 中定义的 name 属性不需要与参数名称匹配,不过两者应该匹配。

    module.exports = async function(context, myTrigger, myInput, myOtherInput) { ... };
    
  • context.bindings 对象的成员的形式。 每个成员由 function.json 中定义的 name 属性命名。

    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);
    };
    
  • 使用 JavaScript arguments 对象以输入的形式。 这实质上与作为参数传递输入相同,但可以动态处理输入。

    module.exports = async function(context) { 
        context.log("This is myTrigger: " + arguments[1]);
        context.log("This is myInput: " + arguments[2]);
        context.log("This is myOtherInput: " + arguments[3]);
    };
    

Outputs

函数可通过多种方式写入输出(direction === "out" 的绑定)。 在所有情况下,function.json 中定义的绑定属性 name 对应于函数中所写入到的对象成员的名称。

可通过以下方式之一将数据分配到输出绑定。 不要结合使用这些方法。

  • [有多个输出时建议使用] 返回对象。 如果使用异步函数/返回 Promise 的函数,可以返回分配有输出数据的对象。 在以下示例中,function.json 中的输出绑定名为“httpResponse”和“queueOutput”。

    module.exports = async function(context) {
        let retMsg = 'Hello, world!';
        return {
            httpResponse: {
                body: retMsg
            },
            queueOutput: retMsg
        };
    };
    

    如果使用同步函数,可以使用 context.done 返回此对象(请参阅示例)。

  • [有单个输出时建议使用] 直接返回值,并使用 $return 绑定名称。 这仅适用于异步函数/返回 Promise 的函数。 请参阅导出异步函数中的示例。
  • context.bindings 赋值 可以直接向 context.bindings 赋值。

    module.exports = async function(context) {
        let retMsg = 'Hello, world!';
        context.bindings.httpResponse = {
            body: retMsg
        };
        context.bindings.queueOutput = retMsg;
        return;
    };
    

绑定数据类型

若要定义输入绑定的数据类型,请使用绑定定义中的 dataType 属性。 例如,若要以二进制格式读取 HTTP 请求的内容,请使用类型 binary

{
    "type": "httpTrigger",
    "name": "req",
    "direction": "in",
    "dataType": "binary"
}

dataType 的选项为 binarystreamstring

上下文对象

运行时使用 context 对象将数据传入和传出函数,并能与其进行通信。 上下文对象可用于从绑定读取和设置数据、写入日志,以及当导出的函数是同步函数时使用 context.done 回调。

context 对象始终是传递给函数的第一个参数。 之所以需要包含此对象,是因为它包含 context.donecontext.log 等重要方法。 可以按个人喜好为对象命名(例如 ctxc)。

// You must include a context, but other arguments are optional
module.exports = function(ctx) {
    // function logic goes here :)
    ctx.done();
};

context.bindings 属性

context.bindings

返回一个包含所有输入和输出数据的已命名对象。 例如,function.json 中的以下绑定定义允许通过 context.bindings.myInput 访问队列的内容和使用 context.bindings.myOutput 将输出分配给队列。

{
    "type":"queue",
    "direction":"in",
    "name":"myInput"
    ...
},
{
    "type":"queue",
    "direction":"out",
    "name":"myOutput"
    ...
}
// myInput contains the input data, which may have properties such as "name"
var author = context.bindings.myInput.name;
// Similarly, you can set your output data
context.bindings.myOutput = { 
        some_text: 'hello world', 
        a_number: 1 };

可以选择使用 context.done 方法而不是 context.binding 对象来定义输出绑定数据(参阅下文)。

context.bindingData 属性

context.bindingData

返回包含触发器元数据和函数调用数据(invocationIdsys.methodNamesys.utcNowsys.randGuid)的命名对象。 有关触发器元数据的示例,请参阅此事件中心示例

context.done 方法

context.done([err],[propertyBag])

让运行时知道代码已完成。 如果函数使用 async function 声明,则你不需要使用 context.done()context.done 回调是隐式调用的。 异步函数在 Node 8 或更高版本(需要 Functions 运行时版本 2.x)中可用。

如果函数不是异步函数,则必须调用 context.done 来告知运行时函数是完整的。 如果缺少它,则执行将会超时。

使用 context.done 方法可向运行时传回用户定义的错误,以及传回包含输出绑定数据的 JSON 对象。 传递给 context.done 的属性将覆盖 context.bindings 对象上设置的任何内容。

// Even though we set myOutput to have:
//  -> text: 'hello world', number: 123
context.bindings.myOutput = { text: 'hello world', number: 123 };
// If we pass an object to the done function...
context.done(null, { myOutput: { text: 'hello there, world', noNumber: true }});
// the done method overwrites the myOutput binding to be: 
//  -> text: 'hello there, world', noNumber: true

context.log 方法

context.log(message)

用于在默认跟踪级别写入到流函数日志。 context.log 中还提供了其他的日志记录方法,用以允许在其他跟踪级别向函数日志进行写入:

方法 说明
error(message) 向错误级日志记录或更低级别进行写入。
warn(message) 向警告级日志记录或更低级别进行写入。
info(message) 向信息级日志记录或更低级别进行写入。
verbose(message) 向详细级日志记录进行写入。

以下示例在警告跟踪级别向日志进行写入:

context.log.warn("Something has happened."); 

可以在 host.json 文件中为日志记录配置跟踪级别阈值。 有关写入日志的详细信息,请参阅下面的写入跟踪输出

将跟踪输出写入到控制台

在 Functions 中,可以使用 context.log 方法将跟踪输出写入到控制台。 在 Functions v2.x 中,使用 console.log 的跟踪输出在函数应用级别捕获。 这意味着来自 console.log 的输出不受限于特定的函数调用,因此不会显示在特定函数的日志中。 在 Functions v1.x 中,不能使用 console.log 写入到控制台。

调用 context.log() 时,消息会在默认跟踪级别(即_信息_跟踪级别)写入到控制台。 以下代码在信息跟踪级别向控制台进行写入:

context.log({hello: 'world'});  

此代码等同于上述代码:

context.log.info({hello: 'world'});  

此代码在错误级别向控制台进行写入:

context.log.error("An error has occurred.");  

因为_错误_是最高跟踪级别,所以,只要启用了日志记录,此跟踪会在所有跟踪级别写入到输出中。

所有 context.log 方法都支持 Node.js util.format 方法支持的同一参数格式。 以下代码使用默认跟踪级别向函数日志进行写入:

context.log('Node.js HTTP trigger function processed a request. RequestUri=' + req.originalUrl);
context.log('Request Headers = ' + JSON.stringify(req.headers));

还可以采用以下格式编写同一代码:

context.log('Node.js HTTP trigger function processed a request. RequestUri=%s', req.originalUrl);
context.log('Request Headers = ', JSON.stringify(req.headers));

为控制台日志记录配置跟踪级别

Functions 允许定义向控制台进行写入时使用的阈值跟踪级别,这使得可以轻松控制从函数向控制台写入跟踪的方式。 若要针对写入到控制台的所有跟踪设置阈值,请在 host.json 文件中使用 tracing.consoleLevel 属性。 此设置应用于 Function App 中的所有函数。 以下示例设置跟踪阈值来启用详细日志记录:

{
    "tracing": {
        "consoleLevel": "verbose"
    }
}  

consoleLevel 的值对应于 context.log 方法的名称。 要为控制台禁用所有跟踪日志记录,请将 consoleLevel 设置为 off。 有关详细信息,请参阅 host.json 参考

HTTP 触发器和绑定

HTTP 和 webhook 触发器以及 HTTP 输出绑定使用请求和响应对象来表示 HTTP 消息。

请求对象

context.req(请求)对象具有以下属性:

属性 说明
body 一个包含请求正文的对象。
headers 一个包含请求标头的对象。
method 请求的 HTTP 方法。
originalUrl 请求的 URL。
params 一个包含请求的路由参数的对象。
query 一个包含查询参数的对象。
rawBody 字符串形式的消息正文。

响应对象

context.res(响应)对象具有以下属性:

属性 说明
body 一个包含响应正文的对象。
headers 一个包含响应标头的对象。
isRaw 指示是否为响应跳过格式设置。
status 响应的 HTTP 状态代码。

访问请求和响应

使用 HTTP 触发器时,可采用多种方式来访问 HTTP 响应和请求对象:

  • 通过 context 对象的 reqres 属性。 采用此方式时,可以使用传统模式通过上下文对象访问 HTTP 数据,而不必使用完整的 context.bindings.name 模式。 以下示例展示了如何访问 context 上的 reqres 对象:

    // You can access your http request off the context ...
    if(context.req.body.emoji === ':pizza:') context.log('Yay!');
    // and also set your http response
    context.res = { status: 202, body: 'You successfully ordered more coffee!' }; 
    
  • 通过已命名的输入和输出绑定。 采用此方式时,HTTP 触发器和绑定的工作方式与其他绑定相同。 以下示例使用已命名的 response 绑定设置响应对象:

    {
        "type": "http",
        "direction": "out",
        "name": "response"
    }
    
    context.bindings.response = { status: 201, body: "Insert succeeded." };
    
  • [仅响应] 通过调用 context.res.send(body?: any) HTTP 响应是使用输入 body 作为响应正文创建的。 隐式调用 context.done()

  • [仅响应] 通过调用 context.done() 有一种特殊的 HTTP 绑定可返回传递到 context.done() 方法的响应。 以下 HTTP 输出绑定定义了一个 $return 输出参数:

    {
      "type": "http",
      "direction": "out",
      "name": "$return"
    }
    
     // Define a valid response object.
    res = { status: 201, body: "Insert succeeded." };
    context.done(null, res);   
    

Node 版本

下表显示了 Functions 运行时的每个主要版本使用的 Node.js 版本:

Functions 版本 Node.js 版本
1.x 6.11.2(运行时锁定)
2.x 活动 LTS 和偶数的_最新_ Node.js 版本(推荐 8.11.1 和 10.14.1)。 使用 WEBSITE_NODE_DEFAULT_VERSION 应用设置来设置版本。

可以通过查看上述应用设置或打印任何函数的 process.version 来查看运行时正在使用的当前版本。

依赖项管理

若要在 JavaScript 代码中使用社区库(如下面的示例所示),需要确保在 Azure 中的 Function App 上安装所有依赖项。

// Import the underscore.js library
var _ = require('underscore');
var version = process.version; // version === 'v6.5.0'

module.exports = function(context) {
    // Using our imported underscore.js library
    var matched_names = _
        .where(context.bindings.myInput.names, {first: 'Carla'});

Note

应当在 Function App 的根目录下定义一个 package.json 文件。 定义该文件将允许应用中的所有函数共享所缓存的相同包,从而获得最佳性能。 如果发生版本冲突,可以通过在具体函数的文件夹中添加一个 package.json 文件来解决冲突。

部署过程中,从源控件中部署 Function App 时,存储库中存在的任何 package.json 文件都将在其文件夹中触发 npm install。 但在通过门户或 CLI 部署时,必须手动安装包。

可通过两种方法在 Function App 上安装包:

使用依赖项部署

  1. 通过运行 npm install 在本地安装所有必需的包。

  2. 部署代码,并确保部署中包含 node_modules 文件夹。

使用 Kudu

  1. 转到 https://<function_app_name>.scm.chinacloudsites.cn

  2. 单击“调试控制台”,选择“CMD”。 >

  3. 转到 D:\home\site\wwwroot,然后将 package.json 文件拖到页面上半部分中的 wwwroot 文件夹上。
    还可采用其他方式将文件上传到 Function App。 有关详细信息,请参阅如何更新 Function App 文件

  4. 上传 package.json 文件后,在 Kudu 远程执行控制台中运行 npm install 命令。
    此操作将下载 package.json 文件中指定的包并重新启动 Function App。

环境变量

在 Functions 中,服务连接字符串等应用设置在执行过程中将公开为环境变量。 可以使用 process.env 访问这些设置,如以下 GetEnvironmentVariable 函数中所示:

module.exports = function (context, myTimer) {
    var timeStamp = new Date().toISOString();

    context.log('Node.js timer trigger function ran!', timeStamp);
    context.log(GetEnvironmentVariable("AzureWebJobsStorage"));
    context.log(GetEnvironmentVariable("WEBSITE_SITE_NAME"));

    context.done();
};

function GetEnvironmentVariable(name)
{
    return name + ": " + process.env[name];
}

可以通过以下几种方法添加、更新和删除函数应用设置:

在本地运行时,可从 local.settings.json 项目文件读取应用设置。

配置函数入口点

function.json 属性 scriptFileentryPoint 可用于配置导出函数的位置和名称。 转译 JavaScript 时,这些属性可能非常重要。

使用 scriptFile

默认情况下通过 index.js(与其对应的 function.json 共享相同父目录的文件)执行 JavaScript 函数。

scriptFile 可用于获取以下示例所示的文件夹结构:

FunctionApp
 | - host.json
 | - myNodeFunction
 | | - function.json
 | - lib
 | | - nodeFunction.js
 | - node_modules
 | | - ... packages ...
 | - package.json

myNodeFunctionfunction.json 应包含 scriptFile 属性,该属性指向包含要运行的导出函数的文件。

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

使用 entryPoint

scriptFile(或 index.js)中,必须使用 module.exports 导出函数才能使其被找到和运行。 默认情况下,触发时执行的函数是该文件的唯一导出(导出名为 runindex)。

可以使用 function.json 中的 entryPoint 配置此项设置,如以下示例所示:

{
  "entryPoint": "logFoo",
  "bindings": [
    ...
  ]
}

Functions v2.x 支持用户函数中的 this 参数,其中的函数代码可能如以下示例所示:

class MyObj {
    constructor() {
        this.foo = 1;
    };

    function logFoo(context) { 
        context.log("Foo is " + this.foo); 
        context.done(); 
    }
}

const myObj = new MyObj();
module.exports = myObj;

请在此示例中务必注意,尽管正在导出对象,但无法保证可保留两次执行之间的状态。

JavaScript 函数的注意事项

使用 JavaScript 函数时,请注意以下各节中的注意事项。

选择单 vCPU 应用服务计划

创建使用应用服务计划的函数应用时,建议选择单 vCPU 计划,而不是选择具有多个 vCPU 的计划。 目前,Functions 在单 vCPU VM 上运行 JavaScript 函数更为高效;使用更大的 VM 不会产生预期的性能提高。 需要时,可以通过添加更多单 vCPU VM 实例来手动扩大,也可以启用自动缩放。 有关详细信息,请参阅手动或自动缩放实例计数

TypeScript 和 CoffeeScript 支持

因为目前还不能直接支持通过运行时自动编译 TypeScript 或 CoffeeScript,因此需要在部署时在运行时外部处理此类支持。

冷启动

对于无服务器托管模型中开发 Azure Functions,冷启动已成为现实。 “冷启动”是指在函数应用处于非活动状态一段时间后进行第一次启动时,将需要较长时间才能启动。 具体而言,对于具有较大依赖项树的 JavaScript 函数,冷启动可能不足以解决问题。 为了加快冷启动过程,请尽量以包文件的形式运行函数。 许多部署方法默认使用包模型中的运行,但如果遇到大规模的冷启动而不是以这种方式运行,则此项更改可以提供明显的改善。

后续步骤

有关详细信息,请参阅以下资源: