Repository: koajs/koa
Pull request #931 (2017-03-08 06:58:37)

docs: create v2 Migration document
closes https://github.com/koajs/koa/pull/920


Patch:
diff --git a/Readme.md b/Readme.md
index e1cd7e9..5d2c2df 100644
--- a/Readme.md
+++ b/Readme.md
@@ -44,11 +44,11 @@ app.listen(3000);
 
 
 ## Middleware
+
 Koa is a middleware framework that can take 3 different kinds of functions as middleware:
 
   * common function
   * async function
-  * generator function
 
 Here is an example of logger middleware with each of the different functions:
 
@@ -78,69 +78,46 @@ app.use((ctx, next) => {
 });
 ```
 
-### GeneratorFunction
-
-To use generator functions, you must use a wrapper such as [co](https://github.com/tj/co) that is no longer supplied with Koa.
-
-```js
-app.use(co.wrap(function *(ctx, next) {
-  const start = new Date();
-  yield next();
-  const ms = new Date() - start;
-  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
-}));
-```
-
-### Old signature middleware (v1.x) - Deprecated
-
-**Old signature middleware (v1.x) support will be removed in v3**
-
-Koa v2.x will try to convert legacy signature, generator middleware on `app.use`, using [koa-convert](https://github.com/koajs/convert).
-It is however recommended that you choose to migrate all v1.x middleware as soon as possible.
-
-```js
-// Koa will convert
-app.use(function *(next) {
-  const start = new Date();
-  yield next;
-  const ms = new Date() - start;
-  console.log(`${this.method} ${this.url} - ${ms}ms`);
-});
-```
+### Koa v1.x Middleware Signature
 
-You could do it manually as well, in which case Koa will not convert.
+The middleware signature changed between v1.x and v2.x.  The older signature is deprecated.
 
-```js
-const convert = require('koa-convert');
+**Old signature middleware support will be removed in v3**
 
-app.use(convert(function *(next) {
-  const start = new Date();
-  yield next;
-  const ms = new Date() - start;
-  console.log(`${this.method} ${this.url} - ${ms}ms`);
-}));
-```
+Please see the [Migration Guide](docs/migration.md) for more information on upgrading from v1.x and
+using v1.x middleware with v2.x.
 
 ## Babel setup
 
-For Node 4.0+ and Babel 6.0 you can setup like this:
+If you're not using `node v7.6+`, we recommend setting up `babel` with [`babel-preset-env`](https://github.com/babel/babel-preset-env):
 
 ```bash
-$ npm install babel-register babel-plugin-transform-async-to-generator --save
+$ npm install babel-register babel-preset-env --save
 ```
 
+Setup `babel-register` in your entry file:
+
 ```js
-// set babel in entry file
-require('babel-register')({
-  plugins: ['transform-async-to-generator']
-});
+require('babel-register');
 ```
 
-Check out an example in koa's [test](test/babel/index.js).
+And have your `.babelrc` setup:
+
+```json
+{
+  "presets": [
+    ["env", {
+      "targets": {
+        "node": true
+      }
+    }]
+  ]
+}
+```
 
 ## Troubleshooting
 
-Check the [Troubleshooting Guide](docs/troubleshooting.md) or [Debugging Koa](docs/guide.md#debugging-koa) in 
+Check the [Troubleshooting Guide](docs/troubleshooting.md) or [Debugging Koa](docs/guide.md#debugging-koa) in
 the general Koa guide.
 
 ## Running tests
diff --git a/docs/migration.md b/docs/migration.md
new file mode 100644
index 0000000..52625b4
--- /dev/null
+++ b/docs/migration.md
@@ -0,0 +1,133 @@
+# Migrating from Koa v1.x to v2.x
+
+## New middleware signature 
+
+Koa v2 introduces a new signature for middleware.
+
+**Old signature middleware (v1.x) support will be removed in v3**
+
+The new middleware signature is:
+
+```js
+// uses async arrow functions
+app.use(async (ctx, next) => {
+  try {
+    await next() // next is now a function
+  } catch (err) {
+    ctx.body = { message: err.message }
+    ctx.status = err.status || 500
+  }
+})
+
+app.use(async ctx => {
+  const user = await User.getById(this.session.userid) // await instead of yield
+  ctx.body = user // ctx instead of this
+})
+```
+
+You don't have to use asynchronous functions - you just have to pass a function that returns a promise. 
+A regular function that returns a promise works too!
+
+The signature has changed to pass `Context` via an explicit parameter, `ctx` above, instead of via
+`this`.  The context passing change makes koa more compatible with es6 arrow functions, which capture `this`.
+
+## Using v1.x Middleware in v2.x
+
+Koa v2.x will try to convert legacy signature, generator middleware on `app.use`, using [koa-convert](https://github.com/koajs/convert).
+It is however recommended that you choose to migrate all v1.x middleware as soon as possible.
+
+```js
+// Koa will convert
+app.use(function *(next) {
+  const start = new Date();
+  yield next;
+  const ms = new Date() - start;
+  console.log(`${this.method} ${this.url} - ${ms}ms`);
+});
+```
+
+You could do it manually as well, in which case Koa will not convert.
+
+```js
+const convert = require('koa-convert');
+
+app.use(convert(function *(next) {
+  const start = new Date();
+  yield next;
+  const ms = new Date() - start;
+  console.log(`${this.method} ${this.url} - ${ms}ms`);
+}));
+```
+
+## Upgrading middleware
+
+You will have to convert your generators to async functions with the new middleware signature:
+
+```js
+app.use(async ctx => {
+  const user = await Users.getById(this.session.user_id);
+  await next();
+  ctx.body = { message: 'some message' };
+})
+```
+
+Upgrading your middleware may require some work. One migration path is to update them one-by-one.
+
+1. Wrap all your current middleware in `koa-convert`
+2. Test
+3. `npm outdated` to see which koa middleware is outdated
+4. Update one outdated middleware, remove using `koa-convert`
+5. Test
+6. Repeat steps 3-5 until you're done
+
+
+## Updating your code
+
+You should start refactoring your code now to ease migrating to Koa v2:
+
+- Return promises everywhere!
+- Do not use `yield*`
+- Do not use `yield {}` or `yield []`.
+  - Convert `yield []` into `yield Promise.all([])`
+  - Convert `yield {}` into `yield Bluebird.props({})`
+
+You could also refactor your logic outside of Koa middleware functions. Create functions like 
+`function* someLogic(ctx) {}` and call it in your middleware as 
+`const result = yield someLogic(this)`.
+Not using `this` will help migrations to the new middleware signature, which does not use `this`.
+
+## Application object constructor requires new 
+
+In v1.x, the Application constructor function could be called directly, without `new` to 
+instantiate an instance of an application.  For example:
+
+```js
+var koa = require('koa');
+var app = module.exports = koa();
+```
+
+v2.x uses es6 classes which require the `new` keyword to be used.
+
+```js
+var koa = require('koa');
+var app = module.exports = new koa();
+```
+
+## ENV specific logging behavior removed
+
+An explicit check for the `test` environment was removed from error handling. 
+
+## Dependency changes
+
+- [co](https://github.com/tj/co) is no longer bundled with Koa.  Require or import it directly.
+- [composition](https://github.com/thenables/composition) is no longer used and deprecated.
+
+## v1.x support
+
+The v1.x branch is still supported but should not receive feature updates.  Except for this migration
+guide, documentation will target the latest version.
+
+## Help out
+
+If you encounter migration related issues not covered by this migration guide, please consider 
+submitting a documentation pull request.
diff --git a/lib/application.js b/lib/application.js
index ec30cdc..4c4dab4 100644
--- a/lib/application.js
+++ b/lib/application.js
@@ -107,8 +107,7 @@ module.exports = class Application extends Emitter {
     if (isGeneratorFunction(fn)) {
       deprecate('Support for generators will be removed in v3. ' +
                 'See the documentation for examples of how to convert old middleware ' +
-                'https://github.com/koajs/koa/blob/master/Readme.md' +
-                '#old-signature-middleware-v1x---deprecated');
+                'https://github.com/koajs/koa/blob/master/docs/migration.md');
       fn = convert(fn);
     }
     debug('use %s', fn._name || fn.name || '-');
diff --git a/lib/context.js b/lib/context.js
index 94b5a42..8e46d20 100644
--- a/lib/context.js
+++ b/lib/context.js
@@ -121,8 +121,16 @@ const proto = module.exports = {
       return;
     }
 
-    // unset all headers, and set those specified
-    this.res._headers = {};
+    const { res } = this;
+
+    // first unset all headers
+    if (typeof res.getHeaderNames === 'function') {
+      res.getHeaderNames().forEach(name => res.removeHeader(name));
+    } else {
+      res._headers = {}; // Node < 7.7
+    }
+
+    // then set those specified
     this.set(err.headers);
 
     // force text/plain
diff --git a/lib/response.js b/lib/response.js
index 87ece39..0e77207 100644
--- a/lib/response.js
+++ b/lib/response.js
@@ -44,7 +44,10 @@ module.exports = {
    */
 
   get header() {
-    return this.res._headers || {};
+    const { res } = this;
+    return typeof res.getHeaders === 'function'
+      ? res.getHeaders()
+      : res._headers || {};  // Node < 7.7
   },
 
   /**


Test patch:
diff --git a/test/context/onerror.js b/test/context/onerror.js
index 61b989d..d567259 100644
--- a/test/context/onerror.js
+++ b/test/context/onerror.js
@@ -1,8 +1,10 @@
 
 'use strict';
 
+const assert = require('assert');
 const request = require('supertest');
 const Koa = require('../..');
+const context = require('../helpers/context');
 
 describe('ctx.onerror(err)', () => {
   it('should respond', done => {
@@ -169,5 +171,23 @@ describe('ctx.onerror(err)', () => {
         .expect('Content-Type', 'text/plain; charset=utf-8')
         .expect('Internal Server Error', done);
     });
+
+    it('should use res.getHeaderNames() accessor when available', () => {
+      let removed = 0;
+      const ctx = context();
+
+      ctx.app.emit = () => {};
+      ctx.res = {
+        getHeaderNames: () => ['content-type', 'content-length'],
+        removeHeader: () => removed++,
+        end: () => {},
+        emit: () => {}
+      };
+
+      ctx.onerror(new Error('error'));
+
+      assert.equal(removed, 2);
+    });
   });
 });
+
diff --git a/test/response/header.js b/test/response/header.js
index a378067..d12816d 100644
--- a/test/response/header.js
+++ b/test/response/header.js
@@ -1,7 +1,9 @@
 
 'use strict';
 
+const request = require('supertest');
 const response = require('../helpers/context').response;
+const Koa = require('../..');
 
 describe('res.header', () => {
   it('should return the response header object', () => {
@@ -10,6 +12,28 @@ describe('res.header', () => {
     res.header.should.eql({ 'x-foo': 'bar' });
   });
 
+  it('should use res.getHeaders() accessor when available', () => {
+    const res = response();
+    res.res._headers = null;
+    res.res.getHeaders = () => ({ 'x-foo': 'baz' });
+    res.header.should.eql({ 'x-foo': 'baz' });
+  });
+
+  it('should return the response header object when no mocks are in use', async () => {
+    const app = new Koa();
+    let header;
+
+    app.use(ctx => {
+      ctx.set('x-foo', '42');
+      header = Object.assign({}, ctx.response.header);
+    });
+
+    await request(app.listen())
+      .get('/');
+
+    header.should.eql({ 'x-foo': '42' });
+  });
+
   describe('when res._headers not present', () => {
     it('should return empty object', () => {
       const res = response();