• 回答 (7)
  • 关注 (3)
  • 查看 (2888)

目前,微信小程序越来越流行,而且功能越来越强大,在性能方面也越做越好。因为能够原生调用小程序提供的组件和 API ,小程序的开发快捷,使用方便,越来越多的产品会落地到微信小程序上。但传统的微信小程序开发,和普通的H5页面开发或者原生APP开发相比,只是改变了前端部分的开发方式,还是离不开厚重的后台开发。 现在的小程序,在后台服务方面,正在往 serverless 趋势方向发力。小程序提供了云厂商的无服务器函数 SCF,使得小程序无需搭建一个后台服务即可运行。如何结合腾讯云开发微信小程序?

萌萌哒小昕玥萌萌哒小昕玥提问于
墨莫末沫陌魔回答于

借鉴 Koa2 的中间件机制实现云函数的路由管理

小程序·云开发的云函数目前更推荐 async/await 的玩法来处理异步操作,因此这里也参考了同样是基于 async/await 的 Koa2 的中间件实现机制。

从上面的一些例子我们可以看出,主要是通过 userouter 两种方法传入路由以及相关处理的中间件。

use 只能传入一个中间件,路由也只能是字符串,通常用于 use 一些所有路由都得使用的中间件

// 不写路由表示该中间件应用于所有的路由
app.use(async (ctx, next) => {

});

app.use('router', async (ctx, next) => {

});

router 可以传一个或多个中间件,路由也可以传入一个或者多个。

app.router('router', async (ctx, next) => {

});

app.router(['router', 'timer'], async (ctx, next) => {
    await next();
}, async (ctx, next) => {
    await next();
}, async (ctx, next) => {

});

不过,无论是 use 还是 router,都只是将路由和中间件信息,通过 _addMiddleware_addRoute 两个方法,录入到 _routerMiddlewares 该对象中,用于后续调用 serve 的时候,层层去执行中间件。

最重要的运行中间件逻辑,则是在 servecompose 两个方法里。

serve 里主要的作用是做路由的匹配以及将中间件组合好之后,通过 compose 进行下一步的操作。比如以下这段节选的代码,其实是将匹配到的路由的中间件,以及 * 这个通配路由的中间件合并到一起,最后依次执行。

let middlewares = (_routerMiddlewares[url]) ? _routerMiddlewares[url].middlewares : [];
// put * path middlewares on the queue head
if (_routerMiddlewares['*']) {
    middlewares = [].concat(_routerMiddlewares['*'].middlewares, middlewares);
}

组合好中间件后,执行这一段,将中间件 compose 后并返回一个函数,传入上下文 this 后,最后将 this.body 的值 resolve,即一般在最后一个中间件里,通过对 ctx.body 的赋值,实现云函数的对小程序端的返回:

const fn = compose(middlewares);

return new Promise((resolve, reject) => {
    fn(this).then((res) => {
        resolve(this.body);
    }).catch(reject);
});

那么 compose 是怎么组合好这些中间件的呢?这里截取部份代码进行分析

function compose(middleware) {
    /**
     * ... 其它代码 
     */
    return function (context, next) {
        // 这里的 next,如果是在主流程里,一般 next 都是空。
        let index = -1;

        // 在这里开始处理处理第一个中间件
        return dispatch(0);

        // dispatch 是核心的方法,通过不断地调用 dispatch 来处理所有的中间件
        function dispatch(i) {
            if (i <= index) {
                return Promise.reject(new Error('next() called multiple times'));
            }

            index = i;

            // 获取中间件函数
            let handler = middleware[i];

            // 处理完最后一个中间件,返回 Proimse.resolve
            if (i === middleware.length) {
                handler = next;
            }

            if (!handler) {
                return Promise.resolve();
            }

            try {
                // 在这里不断地调用 dispatch, 同时增加 i 的数值处理中间件
                return Promise.resolve(handler(context, dispatch.bind(null, i + 1)));
            }
            catch (err) {
                return Promise.reject(err);
            }
        }
    }
}

看完这里的代码,其实有点疑惑,怎么通过 Promise.resolve(handler(xxxx)) 这样的代码逻辑可以推进中间件的调用呢?

首先,我们知道,handler 其实就是一个 async functionnext,就是 dispatch.bind(null, i + 1) 比如这个:

async (ctx, next) => {
    await next();
}

而我们知道,dispatch 是返回一个 Promise.resolve 或者一个 Promise.reject,因此在 async function 里执行 await next(),就相当于触发下一个中间件的调用。

compose 完成后,还是会返回一个 function (context, next),于是就走到下面这个逻辑,执行 fn 并传入上下文 this 后,再将在中间件中赋值的 this.body resolve 出来,最终就成为云函数数要返回的值。

const fn = compose(middlewares);

return new Promise((resolve, reject) => {
    fn(this).then((res) => {
        resolve(this.body);
    }).catch(reject);
});

看到 Promise.resolve 一个 async function,许多人都会很困惑。其实撇除 next 这个往下调用中间件的逻辑,我们可以很好地将逻辑简化成下面这段示例:

let a = async () => {
    console.log(1);
};

let b = async () => {
    console.log(2);

    return 3;
};


let fn = async () => {
    await a();
    return b();
};

Promise.resolve(fn()).then((res) => {
    console.log(res);
});

// 输出
// 1
// 2
// 3

回答过的其他问题

如何通俗的了解kubernetes容器编排?

随着Kubernetes技术热度的不断提升,大容器时代的序幕已经开启。容器技术日新月异,在企业应用实践中得到了不断的发展,高效的运维、管理和部署都成为云服务的重头戏。而与此同时,Kubernetes虽然降低了容器的使用门槛,但其本身的技术门槛却并不低,这种矛盾引起了开发者的关注,...... 展开详请

如何使用gcc编译运行C程序?

示例代码 a.c #include <stdio.h> #include "head.h" /* annotation one annotation two */ extern int N; int main(){ printf("build test N=%d\n",N...... 展开详请

如何实现数据可视化?

使用柱状图往往更好 📷 和柱状图比起来, 气泡图可以在同样的空间表现更多地数据,饼图可以更清晰地表现整体和局部的关系, 树状图能够更好地表现分层的结构。然而, 这些图在简单明了方面都无法与柱状图相比。 在考虑数据可视化设计方案时, 我们要问自己的第一个问题就是:“这个方案比柱状图...... 展开详请

VSCode更改默认终端

您也可以通过按F1VS代码并键入/选择终端:选择默认外壳来选择默认终端。 📷 📷 ... 展开详请

如何解决Java Spring Security:401未授权用于令牌OAuth2端点?

尝试AuthorizationServerConfig使用这个简单的编码器从您的班级更改您的密码编码器(不会加密密码)。因为没有通过加密将您的客户端密钥保存在InMemory存储中 private PasswordEncoder getPasswordEncoder() { ...... 展开详请

不小心把公司数据库的某个表删了,怎么恢复?

当然要快点告诉你组长啊,被骂还是其次的,数据恢复不了你就完了。

一般都有备份的,问你组长就知道了,别着急乱搞越搞越乱。

关于作者

所属标签

扫码关注云+社区

领取腾讯云代金券