{"componentChunkName":"component---src-templates-blog-detail-tsx","path":"/blog/2019-08-12-nodejs","result":{"data":{"currentBlog":{"id":"22c72191-2b2a-5d57-8a0c-aa1616ee296c","frontmatter":{"thumbnail":"https://img.serverlesscloud.cn/2020414/1586850758698-%E5%B0%81%E9%9D%A2%E5%9B%BE%20%284%29.png","authors":["陈杰文"],"categories":["guides-and-tutorials","user-stories"],"date":"2019-08-12T00:00:00.000Z","title":"基于 Node.js 的轻量级云函数功能实现","description":"在万物皆可云的时代，你的应用甚至不需要服务器。云函数功能在各大云服务中均有提供，那么，如何用「无所不能」的 Node.js 实现呢？","authorslink":["https://zhuanlan.zhihu.com/ServerlessGo"],"translators":null,"translatorslink":null,"tags":["Node.js","Serverless"],"keywords":"Serverless","outdated":null},"wordCount":{"words":227,"sentences":56,"paragraphs":56},"fileAbsolutePath":"/opt/build/repo/content/blog/2019-08-12-nodejs.md","fields":{"slug":"/blog/2019-08-12-nodejs/","keywords":["java","koa","nodejs","serverless","无服务器","无服务器架构","云函数","函数","javascript","sandbox","func","执行"]},"html":"<h2 id=\"一、什么是云函数？\"><a href=\"#%E4%B8%80%E3%80%81%E4%BB%80%E4%B9%88%E6%98%AF%E4%BA%91%E5%87%BD%E6%95%B0%EF%BC%9F\" aria-label=\"一、什么是云函数？ permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>一、什么是云函数？</h2>\n<p>函数是诞生于云服务的一个新名词，顾名思义，云函数就是在云端（即服务端）执行的函数。各个云函数相互独立，简单且目的单一，执行环境相互隔离。使用云函数时，开发者只需要关注业务代码本身，其它的诸如环境变量、计算资源等，均由云服务提供。</p>\n<h2 id=\"二、为什么需要云函数？\"><a href=\"#%E4%BA%8C%E3%80%81%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81%E4%BA%91%E5%87%BD%E6%95%B0%EF%BC%9F\" aria-label=\"二、为什么需要云函数？ permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>二、为什么需要云函数？</h2>\n<p>程序员说不想买服务器，于是便有了云服务； 程序员又说连 server 都不想写了，于是便有了云函数。</p>\n<p><strong>Serverless 架构</strong></p>\n<p>通常我们的应用，都会有一个后台程序，它负责处理各种请求和业务逻辑，一般都需要跟网络、数据库等 I/O 打交道。而所谓的无服务器架构，就是把除了业务代码外的所有事情，都交给执行环境处理，开发者不需要知道 server 怎么跑起来，数据库的 api 怎么调用——一切交给外部，在“温室”里写代码即可。</p>\n<p><strong>FaaS</strong></p>\n<p>而云函数，正是 serverless 架构得以实现的途径。我们的应用，将是一个个独立的函数组成，每一个函数里，是一个小粒度的业务逻辑单元。没有服务器，没有 server 程序，“函数即服务”（Functions as a Service）。</p>\n<h2 id=\"三、如何实现？\"><a href=\"#%E4%B8%89%E3%80%81%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%EF%BC%9F\" aria-label=\"三、如何实现？ permalink\" class=\"anchor\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>三、如何实现？</h2>\n<p>由于本实现是应用在一个 CLI 工具里面的，函数声明在开发者的项目文件里，因而大致过程如下：</p>\n<p><strong>1、函数声明与存储</strong></p>\n<ul>\n<li><strong>声明</strong></li>\n</ul>\n<p>我们的目标是让云函数的声明和一般的 js 函数没什么两样：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"51701713593969870000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`module.exports = async function (ctx) {\n    return 'hahha'\n      }`, `51701713593969870000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\">module<span class=\"token punctuation\">.</span><span class=\"token function-variable function\">exports</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">async</span> <span class=\"token keyword\">function</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">ctx</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">return</span> <span class=\"token string\">'hahha'</span>\n      <span class=\"token punctuation\">}</span></code></pre></div>\n<p>由于云函数的执行通常伴随着接口的调用，所以应该要能支持声明 http 方法：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"74916000153838450000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`module.exports = {\n  method: 'POST',\n    handler: async function (ctx) {\n        return 'hahha'\n    }\n};`, `74916000153838450000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\">module<span class=\"token punctuation\">.</span>exports <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>\n  method<span class=\"token punctuation\">:</span> <span class=\"token string\">'POST'</span><span class=\"token punctuation\">,</span>\n    <span class=\"token function-variable function\">handler</span><span class=\"token punctuation\">:</span> <span class=\"token keyword\">async</span> <span class=\"token keyword\">function</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">ctx</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">return</span> <span class=\"token string\">'hahha'</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span></code></pre></div>\n<ul>\n<li><strong>存储</strong></li>\n</ul>\n<p>由于 <code class=\"language-text\">method</code> 等配置，因此编译的时候，需要把上述声明文件 <code class=\"language-text\">require</code> 进来，此时，<code class=\"language-text\">handler</code> 字段是一个 <code class=\"language-text\">Function</code> 类型的对象。可以调用其 <code class=\"language-text\">toString</code> 方法，得到字符串类型的函数体：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"51541092790899180000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`const f = require('./func.js');\nconst method = f.method;\nconst body = f.handler.toString();\n// async function (ctx) {\n//  return 'hahha'\n// }`, `51541092790899180000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\"><span class=\"token keyword\">const</span> f <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">'./func.js'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> method <span class=\"token operator\">=</span> f<span class=\"token punctuation\">.</span>method<span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> body <span class=\"token operator\">=</span> f<span class=\"token punctuation\">.</span>handler<span class=\"token punctuation\">.</span><span class=\"token function\">toString</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token comment\">// async function (ctx) {</span>\n<span class=\"token comment\">//  return 'hahha'</span>\n<span class=\"token comment\">// }</span></code></pre></div>\n<p>有了字符串的函数体，存储就很简单了，直接存在数据库 <code class=\"language-text\">string</code> 类型的字段里即可。</p>\n<p><strong>2、函数执行</strong></p>\n<ul>\n<li>URL</li>\n</ul>\n<p>如果用于前端调用，每个云函数需要有一个对应的 url，以上述声明文件的文件名为云函数的唯一名称的话，可以简单将 url 设计为：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"67659557390621910000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`/f/:funcname`, `67659557390621910000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\"><span class=\"token operator\">/</span>f<span class=\"token operator\">/</span><span class=\"token punctuation\">:</span>funcname</code></pre></div>\n<ul>\n<li><strong>构造独立作用域（重点）</strong></li>\n</ul>\n<p>在 js 世界里，执行一个字符串类型的函数体，有以下这么一些途径：</p>\n<p>1.<code class=\"language-text\">eval</code> 函数</p>\n<ol start=\"2\">\n<li>new Function</li>\n</ol>\n<p>3.<code class=\"language-text\">vm</code> 模块</p>\n<p>那么要选哪一种呢？让我们回顾云函数的特点：<strong>各自独立，互不影响，运行在云端。</strong>关键是将每个云函数放在一个独立的作用域执行，并且没有访问执行环境的权限，因此，最优选择是 nodejs 的 <code class=\"language-text\">vm</code> 模块。关于该模块的使用，可参考官方文档[1]。至此，云函数的执行可以分为三步：</p>\n<p>1. 从数据库获取函数 </p>\n<p>2. 构造<code class=\"language-text\">context</code></p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"40730133645631210000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`// ctx 为 koa 的上下文对象 \nconst sandbox = {\n ctx: {\n   params: ctx.params,\n   query: ctx.query,\n   body: ctx.request.body,\n   userid: ctx.userid,\n },\n promise: null,\n console: console\n}\nvm.createContext(sandbox);`, `40730133645631210000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\"><span class=\"token comment\">// ctx 为 koa 的上下文对象 </span>\n<span class=\"token keyword\">const</span> sandbox <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>\n ctx<span class=\"token punctuation\">:</span> <span class=\"token punctuation\">{</span>\n   params<span class=\"token punctuation\">:</span> ctx<span class=\"token punctuation\">.</span>params<span class=\"token punctuation\">,</span>\n   query<span class=\"token punctuation\">:</span> ctx<span class=\"token punctuation\">.</span>query<span class=\"token punctuation\">,</span>\n   body<span class=\"token punctuation\">:</span> ctx<span class=\"token punctuation\">.</span>request<span class=\"token punctuation\">.</span>body<span class=\"token punctuation\">,</span>\n   userid<span class=\"token punctuation\">:</span> ctx<span class=\"token punctuation\">.</span>userid<span class=\"token punctuation\">,</span>\n <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n promise<span class=\"token punctuation\">:</span> <span class=\"token keyword\">null</span><span class=\"token punctuation\">,</span>\n console<span class=\"token punctuation\">:</span> console\n<span class=\"token punctuation\">}</span>\nvm<span class=\"token punctuation\">.</span><span class=\"token function\">createContext</span><span class=\"token punctuation\">(</span>sandbox<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<ol start=\"3\">\n<li>执行函数得到结果</li>\n</ol>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"92836805381363430000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`const code = \\`func = \\${funcBody}; promise = func(ctx);\\`;\nvm.runInContext(code, sandbox);\nconst data = await sandbox.promise;`, `92836805381363430000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\"><span class=\"token keyword\">const</span> code <span class=\"token operator\">=</span> <span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">func = </span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>funcBody<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\">; promise = func(ctx);</span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">;</span>\nvm<span class=\"token punctuation\">.</span><span class=\"token function\">runInContext</span><span class=\"token punctuation\">(</span>code<span class=\"token punctuation\">,</span> sandbox<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> data <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> sandbox<span class=\"token punctuation\">.</span>promise<span class=\"token punctuation\">;</span></code></pre></div>\n<p>NPM 社区的 <code class=\"language-text\">vm2</code> 模块针对 <code class=\"language-text\">vm</code> 模块的一些安全缺陷做了改进，也可用此模块，思路大抵相同。</p>\n<p><strong>3、引用</strong></p>\n<p>虽然说原则上云函数应当互相独立，各不相欠，但是为了提高灵活性，我们还是决定支持函数间的相互引用，即可以在某云函数中调用另外一个云函数。</p>\n<ul>\n<li><strong>声明</strong></li>\n</ul>\n<p>很简单，加个函数名称的数组字段就好：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"94815521807485580000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`module.exports = {\n  method: 'POST',\n  use: ['func1', 'func2'],\n  handler: async function (ctx) {\n    return 'hahha'\n  }\n};`, `94815521807485580000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\">module<span class=\"token punctuation\">.</span>exports <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>\n  method<span class=\"token punctuation\">:</span> <span class=\"token string\">'POST'</span><span class=\"token punctuation\">,</span>\n  use<span class=\"token punctuation\">:</span> <span class=\"token punctuation\">[</span><span class=\"token string\">'func1'</span><span class=\"token punctuation\">,</span> <span class=\"token string\">'func2'</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n  <span class=\"token function-variable function\">handler</span><span class=\"token punctuation\">:</span> <span class=\"token keyword\">async</span> <span class=\"token keyword\">function</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">ctx</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">return</span> <span class=\"token string\">'hahha'</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span></code></pre></div>\n<ul>\n<li><strong>注入</strong></li>\n</ul>\n<p>也很简单，根据依赖链把函数都找出来，全部挂载在 <code class=\"language-text\">ctx</code> 下就好，深度优先或者广度优先都可以。</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"45282572031655666000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`if (func.use) {\n    const funcs = {};\n    const fnames = func.use;\n    for (let i = 0; i < fnames.length; i++) {\n        const fname = fnames[i];\n        await getUsedFuncs(ctx, fname, funcs);\n    }\n\n    const funcCode = \\`{\n        \\${Object.keys(funcs).map(fname => \\`\\${fname}:\\${funcs[fname]}\\`).join('\\n')}\n    }\\`;\n\n    code = \\`ctx.methods=\\${funcCode};\\${code}\\`;\n} else {\n    code = \\`ctx.methods={};\\${code}\\`;\n}\n\n// 获取所有依赖的函数\nconst getUsedFuncs = async (ctx, funcName, methods) => {\n    const func = getFunc(funcName);\n    methods[funcName] = func.body;\n    if (func.use) {\n        const uses = func.use.split(',');\n        for (let i = 0; i < uses.length; i++) {\n            await getUsedFuncs(ctx,uses[i], methods);\n        }\n    }\n}`, `45282572031655666000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\"><span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>func<span class=\"token punctuation\">.</span>use<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> funcs <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">const</span> fnames <span class=\"token operator\">=</span> func<span class=\"token punctuation\">.</span>use<span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">for</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">let</span> i <span class=\"token operator\">=</span> <span class=\"token number\">0</span><span class=\"token punctuation\">;</span> i <span class=\"token operator\">&lt;</span> fnames<span class=\"token punctuation\">.</span>length<span class=\"token punctuation\">;</span> i<span class=\"token operator\">++</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">const</span> fname <span class=\"token operator\">=</span> fnames<span class=\"token punctuation\">[</span>i<span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n        <span class=\"token keyword\">await</span> <span class=\"token function\">getUsedFuncs</span><span class=\"token punctuation\">(</span>ctx<span class=\"token punctuation\">,</span> fname<span class=\"token punctuation\">,</span> funcs<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n\n    <span class=\"token keyword\">const</span> funcCode <span class=\"token operator\">=</span> <span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">{\n        </span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>Object<span class=\"token punctuation\">.</span><span class=\"token function\">keys</span><span class=\"token punctuation\">(</span>funcs<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">map</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">fname</span> <span class=\"token operator\">=></span> <span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>fname<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\">:</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>funcs<span class=\"token punctuation\">[</span>fname<span class=\"token punctuation\">]</span><span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">join</span><span class=\"token punctuation\">(</span><span class=\"token string\">'\\n'</span><span class=\"token punctuation\">)</span><span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\">\n    }</span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">;</span>\n\n    code <span class=\"token operator\">=</span> <span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">ctx.methods=</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>funcCode<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\">;</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>code<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span> <span class=\"token keyword\">else</span> <span class=\"token punctuation\">{</span>\n    code <span class=\"token operator\">=</span> <span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">ctx.methods={};</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>code<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token comment\">// 获取所有依赖的函数</span>\n<span class=\"token keyword\">const</span> <span class=\"token function-variable function\">getUsedFuncs</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">async</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">ctx<span class=\"token punctuation\">,</span> funcName<span class=\"token punctuation\">,</span> methods</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> func <span class=\"token operator\">=</span> <span class=\"token function\">getFunc</span><span class=\"token punctuation\">(</span>funcName<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    methods<span class=\"token punctuation\">[</span>funcName<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> func<span class=\"token punctuation\">.</span>body<span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>func<span class=\"token punctuation\">.</span>use<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">const</span> uses <span class=\"token operator\">=</span> func<span class=\"token punctuation\">.</span>use<span class=\"token punctuation\">.</span><span class=\"token function\">split</span><span class=\"token punctuation\">(</span><span class=\"token string\">','</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token keyword\">for</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">let</span> i <span class=\"token operator\">=</span> <span class=\"token number\">0</span><span class=\"token punctuation\">;</span> i <span class=\"token operator\">&lt;</span> uses<span class=\"token punctuation\">.</span>length<span class=\"token punctuation\">;</span> i<span class=\"token operator\">++</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n            <span class=\"token keyword\">await</span> <span class=\"token function\">getUsedFuncs</span><span class=\"token punctuation\">(</span>ctx<span class=\"token punctuation\">,</span>uses<span class=\"token punctuation\">[</span>i<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> methods<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<ul>\n<li><strong>依赖循环</strong></li>\n</ul>\n<p>既然可以相互依赖，那必然会可能出现 a→b→c→a 这种循环的依赖情况，所以需要在开发者提交云函数的时候，检测依赖循环。检测的思路也很简单，在遍历依赖链的过程中，每一个单独的链条都记录下来，如果发现当前遍历到的函数在链条里出现过，则发生循环。</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"81319290884360650000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`const funcMap = {};\nflist.forEach((f) => {\n    funcMap[f.name] = f;\n});\n\nconst chain = [];\nflist.forEach((f) => {\n    getUseChain(f, chain);\n});\n\nfunction getUseChain(f, chain) {\n    if (chain.includes(f.name)) {\n        throw new Error(\\`函数发生循环依赖：\\${[...chain, f.name].join('→')}\\`);\n    } else {\n        f.use.forEach((fname) => {\n            getUseChain(funcMap[fname], [...chain, f.name]);\n        });\n    }\n}`, `81319290884360650000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\"><span class=\"token keyword\">const</span> funcMap <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\nflist<span class=\"token punctuation\">.</span><span class=\"token function\">forEach</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">f</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n    funcMap<span class=\"token punctuation\">[</span>f<span class=\"token punctuation\">.</span>name<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> f<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">const</span> chain <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\nflist<span class=\"token punctuation\">.</span><span class=\"token function\">forEach</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">f</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n    <span class=\"token function\">getUseChain</span><span class=\"token punctuation\">(</span>f<span class=\"token punctuation\">,</span> chain<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">function</span> <span class=\"token function\">getUseChain</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">f<span class=\"token punctuation\">,</span> chain</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>chain<span class=\"token punctuation\">.</span><span class=\"token function\">includes</span><span class=\"token punctuation\">(</span>f<span class=\"token punctuation\">.</span>name<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">throw</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Error</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">函数发生循环依赖：</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span><span class=\"token punctuation\">[</span><span class=\"token operator\">...</span>chain<span class=\"token punctuation\">,</span> f<span class=\"token punctuation\">.</span>name<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token function\">join</span><span class=\"token punctuation\">(</span><span class=\"token string\">'→'</span><span class=\"token punctuation\">)</span><span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span> <span class=\"token keyword\">else</span> <span class=\"token punctuation\">{</span>\n        f<span class=\"token punctuation\">.</span>use<span class=\"token punctuation\">.</span><span class=\"token function\">forEach</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">fname</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n            <span class=\"token function\">getUseChain</span><span class=\"token punctuation\">(</span>funcMap<span class=\"token punctuation\">[</span>fname<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">[</span><span class=\"token operator\">...</span>chain<span class=\"token punctuation\">,</span> f<span class=\"token punctuation\">.</span>name<span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p><strong>4、性能</strong></p>\n<p>上述方案中，每次云函数执行的时候，都需要进行一下几步：</p>\n<ol>\n<li>获取函数体</li>\n<li>编译代码</li>\n<li>构造作用域和独立环境</li>\n<li>执行</li>\n</ol>\n<p>步骤 3，因为每次执行的参数都不一样，也会有不同请求并发执行同一个函数的情况，所以作用域 <code class=\"language-text\">ctx</code> 无法复用；</p>\n<p>步骤 4 是必须的，那么可优化点就剩下了 1 和 2。</p>\n<ul>\n<li><strong>代码缓存</strong></li>\n</ul>\n<p><code class=\"language-text\">vm</code> 模块提供了代码编译和执行分开处理的接口，因此每次获取到函数体字符串之后，先编译成 <code class=\"language-text\">Script</code> 对象：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"33931969550043628000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`// ...get code\nconst script = new vm.Script(code);`, `33931969550043628000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\"><span class=\"token comment\">// ...get code</span>\n<span class=\"token keyword\">const</span> script <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">vm<span class=\"token punctuation\">.</span>Script</span><span class=\"token punctuation\">(</span>code<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>执行的时候可以直接传入编译好的 <code class=\"language-text\">Script</code> 对象：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"57413369960662704000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`// ...get sandbox\nvm.createContext(sandbox);\nscript.runInContext(sandbox);\nconst data = await sandbox.promise;`, `57413369960662704000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\"><span class=\"token comment\">// ...get sandbox</span>\nvm<span class=\"token punctuation\">.</span><span class=\"token function\">createContext</span><span class=\"token punctuation\">(</span>sandbox<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\nscript<span class=\"token punctuation\">.</span><span class=\"token function\">runInContext</span><span class=\"token punctuation\">(</span>sandbox<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> data <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> sandbox<span class=\"token punctuation\">.</span>promise<span class=\"token punctuation\">;</span></code></pre></div>\n<ul>\n<li><strong>函数体缓存</strong></li>\n</ul>\n<p>简单的缓存，不需要很复杂的更新机制，定一个时间阈值，超过后拉取新的函数体并编译得到 <code class=\"language-text\">Script</code> 对象，然后缓存起来即可：</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"92558928268080070000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"代码复制成功\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`const cacheFuncs = {};\n// ...get script\ncacheFuncs[funcName] = {\n    updateTime: Date.now(),\n    script,\n};\n\n// cache time: 60 sec\nconst cacheFunc = cacheFuncs[cacheKey];\n\nif (cacheFunc && (Date.now() - cacheFunc.updateTime) <= 60000) {\n    const sandbox = { /*...*/ }\n    vm.createContext(sandbox);\n    cacheFunc.script.runInContext(sandbox);\n    const data = await saandbox.promise;\n    return data;\n} else {\n    // renew cache\n}`, `92558928268080070000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                复制代码<svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\"><span class=\"token keyword\">const</span> cacheFuncs <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n<span class=\"token comment\">// ...get script</span>\ncacheFuncs<span class=\"token punctuation\">[</span>funcName<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>\n    updateTime<span class=\"token punctuation\">:</span> Date<span class=\"token punctuation\">.</span><span class=\"token function\">now</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n    script<span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// cache time: 60 sec</span>\n<span class=\"token keyword\">const</span> cacheFunc <span class=\"token operator\">=</span> cacheFuncs<span class=\"token punctuation\">[</span>cacheKey<span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>cacheFunc <span class=\"token operator\">&amp;&amp;</span> <span class=\"token punctuation\">(</span>Date<span class=\"token punctuation\">.</span><span class=\"token function\">now</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">-</span> cacheFunc<span class=\"token punctuation\">.</span>updateTime<span class=\"token punctuation\">)</span> <span class=\"token operator\">&lt;=</span> <span class=\"token number\">60000</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> sandbox <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span> <span class=\"token comment\">/*...*/</span> <span class=\"token punctuation\">}</span>\n    vm<span class=\"token punctuation\">.</span><span class=\"token function\">createContext</span><span class=\"token punctuation\">(</span>sandbox<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    cacheFunc<span class=\"token punctuation\">.</span>script<span class=\"token punctuation\">.</span><span class=\"token function\">runInContext</span><span class=\"token punctuation\">(</span>sandbox<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">const</span> data <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> saandbox<span class=\"token punctuation\">.</span>promise<span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">return</span> data<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span> <span class=\"token keyword\">else</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token comment\">// renew cache</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<hr>\n<blockquote>\n<p><strong>传送门：</strong></p>\n<ul>\n<li>GitHub: <a href=\"https://github.com/serverless/serverless/blob/master/README_CN.md\">github.com/serverless</a> </li>\n<li>官网：<a href=\"https://serverless.com/\">serverless.com</a></li>\n</ul>\n</blockquote>\n<p>欢迎访问：<a href=\"https://serverlesscloud.cn/\">Serverless 中文网</a>，您可以在 <a href=\"https://serverlesscloud.cn/best-practice\">最佳实践</a> 里体验更多关于 Serverless 应用的开发！</p>","tableOfContents":"<ul>\n<li><a href=\"/blog/2019-08-12-nodejs/#%E4%B8%80%E3%80%81%E4%BB%80%E4%B9%88%E6%98%AF%E4%BA%91%E5%87%BD%E6%95%B0%EF%BC%9F\">一、什么是云函数？</a></li>\n<li><a href=\"/blog/2019-08-12-nodejs/#%E4%BA%8C%E3%80%81%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81%E4%BA%91%E5%87%BD%E6%95%B0%EF%BC%9F\">二、为什么需要云函数？</a></li>\n<li><a href=\"/blog/2019-08-12-nodejs/#%E4%B8%89%E3%80%81%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%EF%BC%9F\">三、如何实现？</a></li>\n</ul>"},"previousBlog":{"id":"ba972f16-ced0-5ef4-8d03-5dadcc85044c","frontmatter":{"thumbnail":"https://img.serverlesscloud.cn/2020414/1586850670017-%E5%B0%81%E9%9D%A2%E5%9B%BE%20%283%29.png","authors":["Anycodes"],"categories":["user-stories"],"date":"2019-08-14T00:00:00.000Z","title":"如何用 Serverless 定制业务告警功能","description":"在使用云产品的时候，我们可能会需要一些业务告警，本文将告诉你如何快速做一个定制化的告警系统。","authorslink":["https://zhuanlan.zhihu.com/ServerlessGo"],"translators":null,"translatorslink":null,"tags":["业务告警","Serverless"],"keywords":"Serverless","outdated":null},"wordCount":{"words":128,"sentences":29,"paragraphs":29},"fileAbsolutePath":"/opt/build/repo/content/blog/2019-08-14-business-alarm.md","fields":{"slug":"/blog/2019-08-14-business-alarm/","keywords":["java","php","serverless","云函数","param","lag","content"]}},"nextBlog":{"id":"8bea29f1-825b-5b54-b276-79b877da385a","frontmatter":{"thumbnail":"https://img.serverlesscloud.cn/2020414/1586850934183-%E5%B0%81%E9%9D%A2%E5%9B%BE%20%285%29.png","authors":["朱理锋"],"categories":["guides-and-tutorials","user-stories"],"date":"2019-08-08T00:00:00.000Z","title":"云函数 + TypeScript + Node.js 最佳实践探索","description":"本文使用了 Typescript 和 nodejs 开发，实现一个部署在腾讯云云函数 SCF 上的小工具","authorslink":["https://zhuanlan.zhihu.com/ServerlessGo"],"translators":null,"translatorslink":null,"tags":["TypeScript","Node.js"],"keywords":"Serverless","outdated":null},"wordCount":{"words":613,"sentences":94,"paragraphs":93},"fileAbsolutePath":"/opt/build/repo/content/blog/2019-08-08-typescript-nodejs.md","fields":{"slug":"/blog/2019-08-08-typescript-nodejs/","keywords":["go","java","nodejs","serverless","serverlesscloud","文件"]}},"recommendBlogs":{"edges":[{"node":{"id":"4300b21c-7209-5256-86ff-0d38e3daec9b","frontmatter":{"thumbnail":"https://main.qcloudimg.com/raw/14f1c8eed372e76c1b139703b2f6d0fa.jpg","authors":["KieranMcCarthy"],"categories":["user-stories","engineering-culture"],"date":"2018-01-09T00:00:00.000Z","title":"我是如何在四年时间里，从厨师转行为 Serverless 应用开发者","description":"我是厨师出身，现在成为了一名 Serverless 应用开发者。","authorslink":["https://serverless.com/author/kieranmccarthy/"],"translators":["Aceyclee"],"translatorslink":["https://www.zhihu.com/people/Aceyclee"],"tags":["应用开发","Serverless"],"keywords":"Serverless 应用开发,Serverless 管理,厨师转行为 Serverless 应用开发者","outdated":null},"wordCount":{"words":285,"sentences":38,"paragraphs":36},"fileAbsolutePath":"/opt/build/repo/content/blog/2018-01-09-from-chef-to-serverless-developer-in-4-years.md","fields":{"slug":"/blog/2018-01-09-from-chef-to-serverless-developer-in-4-years/","keywords":["无服务器","无服务器开发","云函数","学习","Serverless","构建","Framework","开发者","服务器","应用","学位","简历"]}}},{"node":{"id":"665f9ce2-4451-59fd-bf98-1861789d3b3b","frontmatter":{"thumbnail":"https://s3-us-west-2.amazonaws.com/assets.blog.serverless.com/Serverless_logo.png","authors":["AndreaPasswater"],"categories":["guides-and-tutorials","engineering-culture"],"date":"2018-03-19T00:00:00.000Z","title":"如何为无服务器开放源代码项目做贡献","description":"想要为无服务器开放源代码项目做贡献？您可以遵循下面的指南。","authorslink":null,"translators":null,"translatorslink":null,"tags":null,"keywords":null,"outdated":null},"wordCount":{"words":96,"sentences":36,"paragraphs":36},"fileAbsolutePath":"/opt/build/repo/content/blog/2018-03-19-how-contribute-to-serverless-open-source.md","fields":{"slug":"/blog/2018-03-19-how-contribute-to-serverless-open-source/","keywords":["serverless","无服务器","serverless","github","插件","服务器","贡献","示例","blog","contribute"]}}},{"node":{"id":"a3e92579-65c3-5159-937c-32d18c5df7d7","frontmatter":{"thumbnail":"https://s3-us-west-2.amazonaws.com/assets.blog.serverless.com/why-not/why-not-header.png","authors":["AndreaPasswater"],"categories":["guides-and-tutorials","operations-and-observability","engineering-culture"],"date":"2018-03-21T00:00:00.000Z","title":"不适合选择无服务器的情境及原因","description":"无服务器既有优点也有缺点。那么，哪些情境下不适合选择无服务器？原因又是什么呢？","authorslink":null,"translators":null,"translatorslink":null,"tags":null,"keywords":null,"outdated":null},"wordCount":{"words":163,"sentences":43,"paragraphs":43},"fileAbsolutePath":"/opt/build/repo/content/blog/2018-03-21-when-why-not-use-serverless.md","fields":{"slug":"/blog/2018-03-21-when-why-not-use-serverless/","keywords":["faas","react","serverless","spa","无服务器","无服务器函数","无服务器架构","无服务器开发","服务器","twitter","serverless","blockquote","lang","script","en"]}}},{"node":{"id":"6a16520b-7886-582e-9182-64e50712d486","frontmatter":{"thumbnail":"https://s3-us-west-2.amazonaws.com/assets.blog.serverless.com/vendor+choice/serverless-data-portability.jpg","authors":["NickGottlieb"],"categories":["engineering-culture","guides-and-tutorials"],"date":"2018-06-20T00:00:00.000Z","title":"浅谈无服务器、数据锁定和供应商选择","description":"供应商选择是如今 IT 领导者需要考虑的最重要事项，而这一点可利用数据可移植性来实现。","authorslink":null,"translators":null,"translatorslink":null,"tags":null,"keywords":null,"outdated":null},"wordCount":{"words":205,"sentences":33,"paragraphs":33},"fileAbsolutePath":"/opt/build/repo/content/blog/2018-06-20-data-lockin-vendor-choice-portability.md","fields":{"slug":"/blog/2018-06-20-data-lockin-vendor-choice-portability/","keywords":["go","serverless","无服务器","无服务器架构","供应商","serverless","开发人员","数据","锁定","选择","服务"]}}},{"node":{"id":"94741abb-10ba-5db1-9756-cd1d573473fa","frontmatter":{"thumbnail":"https://s3-us-west-2.amazonaws.com/assets.blog.serverless.com/webstorm-ide/streamline-webstorm-serverless2.jpg","authors":["EslamHefnawy"],"categories":["guides-and-tutorials","engineering-culture"],"date":"2018-08-15T00:00:00.000Z","title":"如何使用 WebStorm 简化无服务器工作流程","description":"在本文中，我将和您分享如何使用 WebStorm 进行无服务器特定的 IDE 设置以及如何利用它来极大地加快无服务器工作流程。","authorslink":null,"translators":null,"translatorslink":null,"tags":null,"keywords":null,"outdated":null},"wordCount":{"words":234,"sentences":54,"paragraphs":54},"fileAbsolutePath":"/opt/build/repo/content/blog/2018-08-15-streamline-serverless-workflow-webstorm.md","fields":{"slug":"/blog/2018-08-15-streamline-serverless-workflow-webstorm/","keywords":["nodejs","serverless","无服务器","无服务器开发","serverless","WebStorm","webstorm","服务器","blog","assets"]}}},{"node":{"id":"713a0563-4bf9-5721-bacb-3b4ef609fe4a","frontmatter":{"thumbnail":"https://s3-us-west-2.amazonaws.com/assets.blog.serverless.com/camp-fire/camp-fire-housing-thumb.jpg","authors":["EricWyne"],"categories":["guides-and-tutorials","user-stories"],"date":"2018-12-05T00:00:00.000Z","title":"Serverless Twitter 机器人帮助为坎普山火受灾者安置住房","description":"加利福尼亚州的坎普山火致使数千人流离失所，为此，我构建了一个简单的 Serverless Twitter 机器人来帮助将受灾者安置在临时住房！","authorslink":["https://serverless.com/author/ericwyne/"],"translators":["Aceyclee"],"translatorslink":["zhihu.com/people/Aceyclee"],"tags":null,"keywords":null,"outdated":null},"wordCount":{"words":157,"sentences":26,"paragraphs":26},"fileAbsolutePath":"/opt/build/repo/content/blog/2018-12-05-serverless-twitter-camp-fire.md","fields":{"slug":"/blog/2018-12-05-serverless-twitter-camp-fire/","keywords":["serverless","无服务器","云函数","Serverless","org","住房","Twitter","函数","受灾","机器人","山火"]}}},{"node":{"id":"98602143-b837-5f50-a24f-3b1ec76044d7","frontmatter":{"thumbnail":"https://s3-us-west-2.amazonaws.com/assets.blog.serverless.com/sqquid/sqquid-serverless-thumb.jpg","authors":["RonPeled"],"categories":["user-stories"],"date":"2018-12-17T00:00:00.000Z","title":"SQQUID：100% 无服务器初创公司","description":"SQQUID 将 AWS Lambda 和无服务器框架用于其核心产品和营销网站。我们来看看一个完全无服务器的初创公司是怎样的。","authorslink":null,"translators":null,"translatorslink":null,"tags":null,"keywords":null,"outdated":null},"wordCount":{"words":266,"sentences":42,"paragraphs":42},"fileAbsolutePath":"/opt/build/repo/content/blog/2018-12-17-sqquid-one-hundred-percent-serverless.md","fields":{"slug":"/blog/2018-12-17-sqquid-one-hundred-percent-serverless/","keywords":["go","serverless","无服务器","无服务器架构","服务器","架构","Lambda","集成","FaaS","串行","系统"]}}},{"node":{"id":"29dc2e58-d2ba-56f9-aee1-d21b0bc62e0e","frontmatter":{"thumbnail":"https://s3-us-west-2.amazonaws.com/assets.blog.serverless.com/ao-com-story/ao-serverless-thumbnail.png","authors":["NickGottlieb"],"categories":["user-stories"],"date":"2019-04-24T00:00:00.000Z","title":"AO.com：逐渐转向无服务器优先","description":"AO.com 的 SCV 团队率先尝试无服务器服务。折服于无服务器框架的快速周转时间和低维护成本，整个团队逐渐转向无服务器优先。","authorslink":null,"translators":null,"translatorslink":null,"tags":null,"keywords":null,"outdated":null},"wordCount":{"words":236,"sentences":42,"paragraphs":35},"fileAbsolutePath":"/opt/build/repo/content/blog/2019-04-24-ao-serverless-first.md","fields":{"slug":"/blog/2019-04-24-ao-serverless-first/","keywords":["serverless","无服务器","服务器","团队","Lambda","功能","构建"]}}}],"totalCount":89}},"pageContext":{"isCreatedByStatefulCreatePages":false,"blogId":"22c72191-2b2a-5d57-8a0c-aa1616ee296c","previousBlogId":"ba972f16-ced0-5ef4-8d03-5dadcc85044c","nextBlogId":"8bea29f1-825b-5b54-b276-79b877da385a","categories":["guides-and-tutorials","user-stories"]}}}