site/docs/intro/migration.md
With the official release of Node.js 8 LTS, egg now comes with built-in ES2017 Async Function support.
Though the TJ co has brought async/await programming experience before this, but it also has some inevitable problems:
In the official Egg 2.x:
generator function.async function solutions.One of the Egg's concept is progressive, hence we provide progressive programming experiences to developers.
>=8.9.0).egg version to ^2.0.0 in package.json.Done! Barely with any code changes
yield parts needs to change to await parts() or yield parts()
// old
const parts = ctx.multipart();
while ((part = yield parts) != null) {
// do something
}
// yield parts() also work
while ((part = yield parts()) != null) {
// do something
}
// new
const parts = ctx.multipart();
while ((part = await parts()) != null) {
// do something
}
DO NOT support 1.x role definition, because koa-roles is no longer compatible.
The Context has changed from this to the first argument ctx, the original scope now is the second argument.
// old
app.role.use('user', function () {
return !!this.user;
});
// new
app.role.use((ctx, scope) => {
return !!ctx.user;
});
app.role.use('user', (ctx) => {
return !!ctx.user;
});
Due to the complete compatibility to Egg 1.x, we can finish the upgrade quickly.
But in order to keep the coding style consistent, as well as a better performance improvement and more developer-friendly error stack logs, we suggest developers to make a further upgrade:
yieldable to awaitable in function invoke2.x is compatible to 1.x-styled middleware, so it's still functional without any changes.
(ctx, next) arguments style in callback function
ctx, means context, it is an instance of Contextnext, use await to execute it for the coming logics.async (ctx, next) => {} is not recommended, which prevents anonymous function in error stack.yield next to await next().// 1.x
module.exports = () => {
return function* responseTime(next) {
const start = Date.now();
yield next;
const delta = Math.ceil(Date.now() - start);
this.set('X-Response-Time', delta + 'ms');
};
};
// 2.x
module.exports = () => {
return async function responseTime(ctx, next) {
const start = Date.now();
// Note, differ from the generator function middleware, next is a function, we're executing it here
await next();
const delta = Math.ceil(Date.now() - start);
ctx.set('X-Response-Time', delta + 'ms');
};
};
async was supported in Egg 1.x, thus if the middleware is already async-base, we could skip this section.
co supports yieldable compatibility types:
Despite both generator and async have the same program models, but we may still need to refactor our codes correspondingly after removing co because of the above special handling from co.
We can replace it directly:
function echo(msg) {
return Promise.resolve(msg);
}
yield echo('hi egg');
// change to
await echo('hi egg');
yeild [] is normally used to send concurrent requests, such as:
const [ news, user ] = yield [
ctx.service.news.list(topic),
ctx.service.user.get(uid),
];
In this case, use Promise.all() to wrap it:
const [news, user] = await Promise.all([
ctx.service.news.list(topic),
ctx.service.user.get(uid),
]);
Sometimes yield {} and yield map can also be used to send concurrent requests, but it may be a bit complex in this place because Promise.all doesn't support Object argument.
// app/service/biz.js
class BizService extends Service {
* list(topic, uid) {
return {
news: ctx.service.news.list(topic),
user: ctx.service.user.get(uid),
};
}
}
// app/controller/home.js
const { news, user } = yield ctx.service.biz.list(topic, uid);
It's recommended to use await Promise.all([]):
// app/service/biz.js
class BizService extends Service {
list(topic, uid) {
return Promise.all([
ctx.service.news.list(topic),
ctx.service.user.get(uid),
]);
}
}
// app/controller/home.js
const [news, user] = await ctx.service.biz.list(topic, uid);
If the interfaces are unchangeable, e can do things below as a workaround:
const { news, user } = await app.toPromise(ctx.service.biz.list(topic, uid));
Use async function to replace the above functions, or use app.toAsyncFunction alternatively.
Note
@sindresorhus has written a lot promise-based helpers, use them together with async function could make source code more readable.
App developers just need to update the upgraded plugins by plugin developers, or use egg-bin autod command we've prepared to quickly update.
The following content is for plugin developers, it shows how to update the plugins:
generator function with async function.In some cases, the interface provided by Plugin developers supports both generator and async, normally it's wrapped by co.
async-first to get a better performance and clearer error stacks.Like egg-schedule plugin, it supports generator or async to define the tasks in application level.
// {app_root}/app/schedule/cleandb.js
exports.task = function* (ctx) {
yield ctx.service.db.clean();
};
// {app_root}/app/schedule/log.js
exports.task = async function splitLog(ctx) {
await ctx.service.log.split();
};
Plugin developers could simply wrap the following raw function:
// https://github.com/eggjs/egg-schedule/blob/80252ef/lib/load_schedule.js#L38
task = app.toAsyncFunction(schedule.task);
async in source code. e.g. egg-view-nunjuckspackage.json
egg in devDependencies to ^2.0.0.engines.node to >=8.0.0.ci.version to 8, 9, reinstall dependencies to generate new travis config files.README.md with async function.test/fixtures to async function, and it's recommended to create a PR for code preview.In case the previous versions still requires maintenance:
1.x version.publishConfig.tag property in package.json to release-1.x in previous version.release-1.x when publishing, so users may use npm i egg-xx@release-1 to import the old version.