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

2018-03-27 12:14:52 +08:00
 waibunleung

问题 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 的场景或者例子?

5998 次点击
所在节点    Node.js
32 条回复
yamedie
2018-03-27 12:24:23 +08:00
我也想问。
zhouyg
2018-03-27 13:00:48 +08:00
返回 promise 是因为要配合 await 使用
cheetah
2018-03-27 13:15:37 +08:00
首先你要了解 async/await 是什么:async/await 是用类似同步的语法完成异步操作,以解决 callback hell 的问题
whypool
2018-03-27 14:11:13 +08:00
因为你不用 async,就得写回调了
这玩意也只是约等于同步
因为 node 里面的 loop 都是异步的,真正的同步阻塞线程什么的,木有
cloudzqy
2018-03-27 15:22:35 +08:00
看了几遍才看懂你的疑问,其实光看阮一峰这个例子好有误导性呀。
“迄今为止,所有例子的中间件都是同步的,不包含异步操作。如果有异步操作(比如读取数据库),中间件就必须写成 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 是不是异步,执行顺序都不会变。
VDimos
2018-03-27 16:04:30 +08:00
Koa2 已经不推荐使用 Generator 函数,换成 Async 函数了。另外,Koa 用的是洋葱模型,所以理论上是得加上 await 的,不加的话,next 会被放到异步里面去了,就不是洋葱模型了
waibunleung
2018-03-27 18:22:10 +08:00
我觉得你们都没有真正能解答我的问题,我知道 async/await 以及 promise 的用法,只是不明白为什么 next 要返回 promise,为什么中间件要写成 async 的形式,因为如果全部都不写 async 的话也能达到目的,仅仅是为了配合 next 返回的 promise 而使用 await 进而要用 async 包裹起函数作为中间件这种说法显然不能说服我。。。另外 @cloudzqy 的回答差不多是那么个意思但是我还是不能尽然明白...
iugo
2018-03-27 18:26:53 +08:00
如果 next() 不需要 await, 就没有什么意义.
cloudzqy
2018-03-27 18:36:55 +08:00
@waibunleung
先考虑目的:实现洋葱模型,这是 koa 基本思想,理由够充分,可以从阮一峰的例子了解这个模型。
然后再考虑方式,用 async。如果不返回 promise,不用 async,当有中间件用异步的时候,无法实现洋葱模型,如例 2。或者你可以自己试试?
当然,回调,promise,generator,async 都可以实现,但是 async 更优雅。
waibunleung
2018-03-27 18:44:31 +08:00
@iugo 你说没什么意义的意思是?能否举一下具体的例子?
Torpedo
2018-03-27 18:47:14 +08:00
@waibunleung 从错误处理的角度来说,如果你的中间件有一个不返回 promise,那么你的
try{
next()
} catch(e){
}
就不能捕获错误。
所以即使同步的中间件,也应该写成这种。
(ctx,next)=>{
xxx
return next().then(xxx);
}
waibunleung
2018-03-27 18:49:52 +08:00
@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:不是想做伸手党,只是想找个人验证一下想法...
waibunleung
2018-03-27 18:53:03 +08:00
@Torpedo 你的这种写法返回的还是 promise,是属于 koa2 中支持的中间件写法之一,其实跟 await 没有太大区别,这样我还是没有能明白对我的疑问有什么帮助....(哭)
iugo
2018-03-27 19:03:25 +08:00
@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()
})
```

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

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

我需要的是最外层的>>one 最先执行,然后<<one 最后执行,2 的输出不符合模型
per
2018-03-27 19:15:48 +08:00
如果你的代码里有请求 ajax 的操作。你不用异步的话就乱套了
zhouyg
2018-03-27 19:38:58 +08:00
还有一种方法,参考 koa 源码,然后去实现一个简单版本的 koa。
这样就可以理解 koa 所处理的场景,以及 koa 如此实现的原因
千言万语不如造一遍轮子
VDimos
2018-03-27 20:01:59 +08:00
之所以使用 async 的原因,是因为它可以 await 一个 Promise。这样就是完美的中间件了。阮一峰那个 blog 应该还是停留在 Koa1.0 的时代,现在 Koa2.x 已经提倡使用 async 了。async 天然的可以等待一个 Promise 执行完成。你想象以下,你现在有个需求,接收到用户上传的文件,然后处理文件以后返回提示信息。如果不使用 async,那么你在 Promise 还没执行完就会返回信息了。现在只需要 await next()。然后再返回信息。其实本质上就是把整个过程变成了同步执行的了,但是这样做的好处是选择面更强了,因为它满足了洋葱一层一层的模型。还有个原因是因为这是一个约定,如果你调用的其他中间件使用了 async,那么你不使用,那么将无法等待这个中间件的执行。换句话说,async 把一系列的异步函数给顺序调用了,如果不是 async 的话,那么所有的异步函数将没有任何关系。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/441745

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX