-
-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
The context can not work with _.cloneDeep #1600
Comments
Is there a specific usage scenario that requires clone ctx? |
In our case, it happens when we use async_hooks and sequelize at the same time. At the async_hooks part, we need to use it to pass the ctx, so there is no need to add the ctx parameter to every function. const { AsyncLocalStorage } = require('async_hooks')
const Koa = require('koa')
const localStorage = new AsyncLocalStorage();
const app = new Koa();
app.use((ctx, next) => {
localStorage.run({ ctx }, next)
});
app.use((ctx) => {
ctx.body = fn1()
});
const fn1 = () => fn2()
const fn2 = () => fn3()
const fn3 = () => {
const { ctx } = localStorage.getStore()
// do something
return ctx.query
}
app.listen(3000); At the sequelize part, we need to use the There are two important things:
const { Sequelize } = require('sequelize')
const dialectModule = {
intervalId: setInterval(() => {
console.log('some thing')
}, 1000)
}
const sequelize = new Sequelize({
dialectModule
})
// It will call _.cloneDeep() to dialectModule.
sequelize.query('SELECT 1 = 1') In the end, our code looks like the following: const { AsyncLocalStorage } = require('async_hooks')
const Koa = require('koa')
const { Sequelize } = require('sequelize')
const localStorage = new AsyncLocalStorage();
const app = new Koa();
app.use((ctx, next) => {
localStorage.run({ ctx }, next)
});
app.use(async (ctx) => {
ctx.body = await someController()
});
const someController = async () => someService()
const someService = async () => someModel()
const someModel = async () => {
const { ctx } = localStorage.getStore()
const sequelize = new Sequelize({
dialectModule: {
intervalId: setInterval(() => {
console.log('some thing')
}, 1000)
}
})
console.log(ctx.query)
// TypeError: Cannot read property 'length' of undefined
return await sequelize.query('SELECT 1 = 1')
}
app.listen(3000); It will be thrown when querying sql, even if the async_hooks has no business with it. The reason is
|
An example without sequelize. const { AsyncLocalStorage } = require('async_hooks')
const _ = require('lodash')
const Koa = require('koa')
const localStorage = new AsyncLocalStorage();
const app = new Koa();
app.use((ctx, next) => {
localStorage.run({ ctx }, next)
});
app.use(async (ctx) => {
ctx.body = await someController()
});
const someController = async () => someService()
const someService = async () => someModel()
const someModel = async () => {
// TypeError: Cannot read property 'length' of undefined
_.cloneDeep(setTimeout(() => {}, 1000))
return { some: 'thing' }
}
app.listen(3000); |
The reason for this error is that length is a delegated property of ctx. So when lodash tries to read it to see if the object has a length, delegate getter is actually called and I guess as the properties are not fully cloned it fails. I guess this is fixed in newer version of lodash as it checks to see if the property is not a function first. |
Test codes:
I found it was caused by the code here https://github.com/koajs/koa/blob/19f3353e5da6a0d52fb968a9429523585e42a288/lib/context.js
The simple code is like this:
When execute
_.cloneDeep(ctx)
,app
will also be cloned. Butapp.context
can not be clone, becauselodash
needs thelength
proproty to decide whether obj is array-like https://github.com/lodash/lodash/blob/2da024c3b4f9947a48517639de7560457cd4ec6c/isArrayLike.jsI think maybe we should do some special operations for
length
property, just like this:The text was updated successfully, but these errors were encountered: