首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Node.js
Express
PPA for Ubuntu
ppa:chris-lea/node.js
V2EX  ›  Node.js

koa2 框架中的中间件同步还是异步的问题?

  •  1
     
  •   waibunleung · 118 天前 · 2656 次点击
    这是一个创建于 118 天前的主题,其中的信息可能已经有所发展或是发生改变。

    问题 1:为什么 koa2 框架中的中间件要用 async 的形式写,很少见用同步模式(即不加 async)? 如:

    app.use(async (ctx, next) => {
      const start = new Date()
      await next()
      const ms = new Date() - start
      console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)
    })
    

    查阅了一些资料,看到阮一峰的 koa 教程中,有同步写法的例子:

    const one = (ctx, next) => {
      console.log('>> one');
      next();
      console.log('<< one');
    }
    
    const two = (ctx, next) => {
      console.log('>> two');
      next(); 
      console.log('<< two');
    }
    
    const three = (ctx, next) => {
      console.log('>> three');
      next();
      console.log('<< three');
    }
    
    app.use(one);
    app.use(two);
    app.use(three);
    

    这里完全没有 async 的出现,阮神只是在后面提到: “迄今为止,所有例子的中间件都是同步的,不包含异步操作。如果有异步操作(比如读取数据库),中间件就必须写成 async 函数。”

    接着找资料发现 koa2 中间件的 next 返回的是 promise,因为源码中的 dispatch(i)返回的就是 promise, 这样我又有一个疑问, 问题 2:为什么要设计成返回 promise ?不返回 promise 就达不到中间件串联的效果吗? 因为中间件的执行在理解上是一个同步的过程,所以设计成异步要怎么去理解??

    问题 3:是不是用 koa2 写的代码都存在很多 async 的 function(公司项目代码到处都是 async 的,返回输出 json 的时候也需要 async 吗?)? 有没有不需要写 async 的场景或者例子?

    32 回复  |  直到 2018-03-28 14:22:18 +08:00
        1
    yamedie   118 天前 via Android
    我也想问。
        2
    zhouyg   118 天前
    返回 promise 是因为要配合 await 使用
        3
    cheetah   118 天前
    首先你要了解 async/await 是什么:async/await 是用类似同步的语法完成异步操作,以解决 callback hell 的问题
        4
    whypool   118 天前
    因为你不用 async,就得写回调了
    这玩意也只是约等于同步
    因为 node 里面的 loop 都是异步的,真正的同步阻塞线程什么的,木有
        5
    cloudzqy   117 天前   ♥ 1
    看了几遍才看懂你的疑问,其实光看阮一峰这个例子好有误导性呀。
    “迄今为止,所有例子的中间件都是同步的,不包含异步操作。如果有异步操作(比如读取数据库),中间件就必须写成 async 函数。”
    只要有一个是异步的,如果要保持这个例子的输出,就必须要用 async 和 await。

    假如这个例子只有 one 和 two,把 two 换成异步
    例一
    ```
    const one = (ctx, next) => {
    console.log('>> one');
    next();
    console.log('<< one');
    }
    const two = (ctx, next) => {
    console.log('>> two');
    setTimemout(() => {
    next();
    console.log('<< two');
    });
    }
    ```
    你这个时候输出的将会是 >> one >> two <<one <<two。
    然而中间件希望的输出是 >> one >> two << two << one。
    所以你需要
    例二
    ```
    const one = async (ctx, next) => {
    console.log('>> one');
    await next();
    console.log('<< one');
    }
    const two = (ctx, next) => {
    console.log('>> two');
    setTimemout(() => {
    next();
    console.log('<< two');
    });
    }
    ```
    这就是为什么 next 要返回 promise。只有全部 promise 才能达到你预期的状态。
    三个问题一起解答,当你期望的执行顺序是例一的时候可以不用 async,这样比较混乱,因为你不知道你后面的中间件有多少异步多少同步,无法预测执行顺序,所以 koa 的中间件思想就是例二这种,不管 two 是不是异步,执行顺序都不会变。
        6
    VDimos   117 天前
    Koa2 已经不推荐使用 Generator 函数,换成 Async 函数了。另外,Koa 用的是洋葱模型,所以理论上是得加上 await 的,不加的话,next 会被放到异步里面去了,就不是洋葱模型了
        7
    waibunleung   117 天前
    我觉得你们都没有真正能解答我的问题,我知道 async/await 以及 promise 的用法,只是不明白为什么 next 要返回 promise,为什么中间件要写成 async 的形式,因为如果全部都不写 async 的话也能达到目的,仅仅是为了配合 next 返回的 promise 而使用 await 进而要用 async 包裹起函数作为中间件这种说法显然不能说服我。。。另外 @cloudzqy 的回答差不多是那么个意思但是我还是不能尽然明白...
        8
    iugo   117 天前
    如果 next() 不需要 await, 就没有什么意义.
        9
    cloudzqy   117 天前 via Android
    @waibunleung
    先考虑目的:实现洋葱模型,这是 koa 基本思想,理由够充分,可以从阮一峰的例子了解这个模型。
    然后再考虑方式,用 async。如果不返回 promise,不用 async,当有中间件用异步的时候,无法实现洋葱模型,如例 2。或者你可以自己试试?
    当然,回调,promise,generator,async 都可以实现,但是 async 更优雅。
        10
    waibunleung   117 天前
    @iugo 你说没什么意义的意思是?能否举一下具体的例子?
        11
    Torpedo   117 天前
    @waibunleung 从错误处理的角度来说,如果你的中间件有一个不返回 promise,那么你的
    try{
    next()
    } catch(e){
    }
    就不能捕获错误。
    所以即使同步的中间件,也应该写成这种。
    (ctx,next)=>{
    xxx
    return next().then(xxx);
    }
        12
    waibunleung   117 天前
    @cloudzqy 我提问之前就试过,这是我的测试代码:
    const one = (ctx, next) => {
    console.log('>> one');
    next();
    console.log('<< one');
    }

    const two = async (ctx, next) => {
    console.log('>> two');
    await next();
    console.log('<< two');
    }

    const three = async (ctx, next) => {
    console.log('>> three');
    await next();
    console.log('<< three');
    }

    app.use(one);
    app.use(two);
    app.use(three);

    //output:
    >> one
    >> two
    >> three
    << one
    << three
    << two

    当全部不使用 async 的时候,是能按照洋葱模型返回的

    或者你能不能解释一下我这样测试的执行过程是怎么样的,看看是否与我的想法相符?
    ps:不是想做伸手党,只是想找个人验证一下想法...
        13
    waibunleung   117 天前
    @Torpedo 你的这种写法返回的还是 promise,是属于 koa2 中支持的中间件写法之一,其实跟 await 没有太大区别,这样我还是没有能明白对我的疑问有什么帮助....(哭)
        14
    iugo   117 天前
    @waibunleung 比如你说的例子:

    ```
    app.use(async (ctx, next) => {
    const start = new Date()
    next()
    const ms = new Date() - start
    console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)
    })

    app.use(async (ctx, next) => {
    return new Promise(resolve => {
    setTimeout(function(){
    resolve("🙃");
    }, 1000);
    });
    await next()
    })
    ```

    这时候你再看看第一个中间件执行时间是多少.
        15
    Torpedo   117 天前
    @waibunleung 从能运行的角度是可以的。但是从错误处理的角度,如果你不是每个中间件都返回一个 promise,那么你的中间件错误处理就可能有问题。
    如果有一个中间件写成同步的模式,那么这中间件后面的错误就不能被
    try{
    next()
    } catch(e){
    }
    捕获。
        16
    iugo   117 天前
    如果所有中间件都是同步的. 那你不用异步也可以.

    一旦之后的中间件可能是异步的, 那么前面的中间件都要是异步的, 并且要 await(等待) next() 执行完成.
        17
    cloudzqy   117 天前 via Android
    @waibunleung
    你说的能,是因为后面的中间件没有异步,而后端代码有异步才是普遍情况,假如有异步:
    1. 使用 await next()会等你后面的中间件的异步全部执行完,再执行 next()后面的代码:
    >>one
    ...其他中间件,包含同步和异步...
    <<one
    2. 而不使用 await 的情况,执行顺序是:
    >>one
    ...其他中间件的同步...
    <<one
    其他中间件的异步

    我需要的是最外层的>>one 最先执行,然后<<one 最后执行,2 的输出不符合模型
        18
    per   117 天前 via iPhone
    如果你的代码里有请求 ajax 的操作。你不用异步的话就乱套了
        19
    zhouyg   117 天前
    还有一种方法,参考 koa 源码,然后去实现一个简单版本的 koa。
    这样就可以理解 koa 所处理的场景,以及 koa 如此实现的原因
    千言万语不如造一遍轮子
        20
    VDimos   117 天前
    之所以使用 async 的原因,是因为它可以 await 一个 Promise。这样就是完美的中间件了。阮一峰那个 blog 应该还是停留在 Koa1.0 的时代,现在 Koa2.x 已经提倡使用 async 了。async 天然的可以等待一个 Promise 执行完成。你想象以下,你现在有个需求,接收到用户上传的文件,然后处理文件以后返回提示信息。如果不使用 async,那么你在 Promise 还没执行完就会返回信息了。现在只需要 await next()。然后再返回信息。其实本质上就是把整个过程变成了同步执行的了,但是这样做的好处是选择面更强了,因为它满足了洋葱一层一层的模型。还有个原因是因为这是一个约定,如果你调用的其他中间件使用了 async,那么你不使用,那么将无法等待这个中间件的执行。换句话说,async 把一系列的异步函数给顺序调用了,如果不是 async 的话,那么所有的异步函数将没有任何关系。
        21
    waibunleung   117 天前
    @VDimos 不知道你有没有见过那个 blog,那个 blog 一直在更新,我看到的文章是 20117 年下半年的,这个时候怎么都是 koa2 了吧?而且我读你的话的前半段,意思就是因为要等待 next 返回的 promise,所以用 await,因为用 await,所以要 async,我觉得这不是一个说得过去的解释....
        22
    hzzhzzdogee   117 天前
    可以用同步, 就变得像 express
        23
    hzzhzzdogee   117 天前
    有那么好用的 async await, 干嘛不用. async 正是 koa 期望的呀
        24
    waibunleung   117 天前
    @hzzhzzdogee 此话怎讲呢?
        25
    VDimos   117 天前 via Android
    @waibunleung koa 用的洋葱模型,要求一层层进去再一层层出来。nodejs 是异步的,koa 遵循了这个原则。而以前的异步最大的问题在于,如果要实现洋葱模型,就必须写大量的回调函数。因此就算是 koa1 也是采用了 generator 这种折中的方法。如果你使用过 co,你就会对洋葱模型有感觉了。其实 koa1 内部就是调用了 co。koa2 改用了新的 async 编写,async 实现的功能本身就是 generator 加 co。你如果用非 async 函数,koa 内部会把它转换成一个 async 函数的。
        26
    kohos   117 天前
    1. 函数前面加 async 的原因是因为函数里面用了 await,没用的话不加也行;
    2. 返回 Promise 的函数前面加个 await 就能得到异步返回的结果,使用起来和同步一样(实际是异步);
    3. koa2 的特性就是用 async/await/Promise 代替以前的回调,使代码更简洁易懂;
    以前那套回调,用 Promise 包装一下就可以拿来给 koa2 使用;
    不用 async/await/Promise 还继续用回调函数的话,不如继续用 expressjs
        27
    POPOEVER   117 天前
    用不用 async/await 跟用 express 或是 koa 有关系吗?我在 express 里面也会用 async 啊
        28
    leemove   117 天前
    https://leemove.github.io/2018/03/19/koa-compose/#more 这个就是 Koa 中间件的原理....看了这个应该就不会疑惑了..
        29
    waibunleung   117 天前
    @POPOEVER 我的意思是,koa2 的中间件几乎都是异步的写法,就不存在同步的写法或者场景吗?为什么要全部安排成异步的?
        30
    waibunleung   117 天前
    @leemove 我看过更详细的原理分析,就是看了才不明不为什么会安排成异步的,就是为什么 dispatch(i)要返回 promise 而不是普通函数或者对象,按照 middleware[]的顺序执行递归执行下去,应该可以达到洋葱模型吧?那这里的 promise 的意义是什么?
        31
    connection   117 天前
    问题 2:promise 一个程度上可以使得异步更精准,也就是在失败成功都可以获得它的状态改变。promise 也有助于回调的扁平。使用 promise 的话,promise 链条状,可以使得外层中间件可以获得内层中间件的状态。在错误捕获上也是有优势.
    以前版本的 co+generator+thunkify 约等于 async/await
    问题 3:事实上如果你的下一个中间件或者当前代码是不需要异步操作的,比如直接是返回的一些硬编码,是不用 async await 的
        32
    leemove   116 天前
    @waibunleung 设计成同步的那一旦一个 next 执行失败,就会影响整个同步执行的过程,错误处理不够优雅.而借助 promise 的特性,可以便于管理错误.
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   鸣谢   ·   实用小工具   ·   3713 人在线   最高记录 3762   ·  
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.1 · 24ms · UTC 06:16 · PVG 14:16 · LAX 23:16 · JFK 02:16
    ♥ Do have faith in what you're doing.
    沪ICP备16043287号-1