Skip to content

Commit

Permalink
feat: support promises in middleware
Browse files Browse the repository at this point in the history
  • Loading branch information
amurdock authored and bcoe committed Jan 28, 2019
1 parent 64a0d7e commit f3a4e4f
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 11 deletions.
18 changes: 18 additions & 0 deletions docs/advanced.md
Expand Up @@ -480,6 +480,24 @@ const normalizeCredentials = (argv) => {
}
```

### Example Async Credentials Middleware

This example is exactly the same however it loads the `username` and `password` asynchronously.

#### Middleware function

```
const { promisify } = require('util') // since node 8.0.0
const readFile = promisify(require('fs').readFile)
const normalizeCredentials = (argv) => {
if (!argv.username || !argv.password) {
return readFile('~/.credentials').then(data => JSON.parse(data))
}
return {}
}
```

#### yargs parsing configuration

```
Expand Down
44 changes: 33 additions & 11 deletions lib/command.js
Expand Up @@ -229,17 +229,35 @@ module.exports = function command (yargs, usage, validation, globalMiddleware) {

if (commandHandler.handler && !yargs._hasOutput()) {
yargs._setHasOutput()
if (commandHandler.middlewares.length > 0) {
const middlewareArgs = commandHandler.middlewares.reduce(function (initialObj, middleware) {
return Object.assign(initialObj, middleware(innerArgv))
}, {})
Object.assign(innerArgv, middlewareArgs)
}
const handlerResult = commandHandler.handler(innerArgv)
if (handlerResult && typeof handlerResult.then === 'function') {
handlerResult.then(
null,
(error) => yargs.getUsageInstance().fail(null, error)

const middlewares = commandHandler.middlewares || []

innerArgv = middlewares
.reduce((accumulation, middleware) => {
if (isPromise(accumulation)) {
return accumulation
.then(initialObj =>
Promise.all([initialObj, middleware(initialObj)])
)
.then(([initialObj, middlewareObj]) =>
Object.assign(initialObj, middlewareObj)
)
} else {
const result = middleware(innerArgv)

return isPromise(result)
? result.then(middlewareObj => Object.assign(accumulation, middlewareObj))
: Object.assign(accumulation, result)
}
}, innerArgv)

const handlerResult = isPromise(innerArgv)
? innerArgv.then(argv => commandHandler.handler(argv))
: commandHandler.handler(innerArgv)

if (isPromise(handlerResult)) {
handlerResult.catch(error =>
yargs.getUsageInstance().fail(null, error)
)
}
}
Expand Down Expand Up @@ -431,3 +449,7 @@ module.exports = function command (yargs, usage, validation, globalMiddleware) {

return self
}

function isPromise(maybePromise) {
return maybePromise instanceof Promise
}
32 changes: 32 additions & 0 deletions test/command.js
Expand Up @@ -1458,4 +1458,36 @@ describe('Command', () => {
return done()
})
})

// addresses https://github.com/yargs/yargs/issues/1237
it('fails when the promise returned by the middleware rejects', (done) => {
const error = new Error()
const handlerErr = new Error('should not have been called')
yargs('foo')
.command('foo', 'foo command', noop, (yargs) => done(handlerErr), [ (yargs) => Promise.reject(error) ])
.fail((msg, err) => {
expect(msg).to.equal(null)
expect(err).to.equal(error)
done()
})
.argv
})

it('calls the command handler when all middleware promises resolve', (done) => {
const middleware = (key, value) => () => new Promise((resolve, reject) => {
setTimeout(() => {
return resolve({ [key]: value })
}, 5)
})
yargs('foo hello')
.command('foo <pos>', 'foo command', () => {}, (yargs) => {
yargs.hello.should.equal('world')
yargs.foo.should.equal('bar')
done()
}, [ middleware('hello', 'world'), middleware('foo', 'bar') ])
.fail((msg, err) => {
return done(Error('should not have been called'))
})
.argv
})
})

0 comments on commit f3a4e4f

Please sign in to comment.