Skip to content
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

Promise support for Nano - Issue 98 #102

Merged
merged 37 commits into from
Jul 24, 2018
Merged

Promise support for Nano - Issue 98 #102

merged 37 commits into from
Jul 24, 2018

Conversation

glynnbird
Copy link
Contributor

@glynnbird glynnbird commented Jul 13, 2018

Overview

Aimed at a v7.x release of Nano, this PR makes Nano return a Promise instead of a Request object as its return value. See #98. This brings Nano into the modern world without breaking code that uses the callback style. It only breaks code that relied on the events emitted from the Request object or that piped the stream to another stream.

Work done in this PR:

  • most functions return a Promise
  • new ...AsStream| functions are availble for operations that return lots of data. These return a Request object
  • test suite updated
  • README changed to reflect the new API
  • TypeScript definitions updated too
  • written migration_6_to_7 guide

Testing recommendations

The tests have been updated to reflect the change. Instantiate a Nano instance and most operations will return a Promise:

var nano = require('.')
var n = nano('http://localhost:5984')
var db = n.db.use('animals')
db.list({start_key:'cat1',end_key:'cat4'})
  .then(console.log)
  .catch(console.error)

GitHub issue number

#98

Related Pull Requests

#88

Checklist

  • Code is written and works correctly;
  • Changes are covered by tests;
  • Documentation reflects the changes;

Copy link
Member

@garrensmith garrensmith left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great work. Just some smaller changes and then I think this is good to go.


// Do not wait for the index to finish building to return results.
stale?: boolean
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow nice work. The definitions are nice and clear now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

lib/nano.js Outdated
var responseHandler = function(req, opts, resolve, reject, callback) {

return function(e, h, b) {
var parsed;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we only supporting more modern nodejs versions. You can use let for var. But ideally use a const. So when you define an object use const.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't sure how far to go with the modernisation of the code. If we're going to use const here, then I'll take a look at doing const/let everywhere else too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well maybe do that in a separate PR.
Lets get this landed. Then do a modernisation and then do a release for 7.0.0

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

lib/nano.js Outdated

return function(e, h, b) {
var parsed;
var rh = h && h.headers || {};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be simplified to:

const responseHeader = Object.assign({
   uri: req.uri,
   statusCode: h.statusCode
}, h.headers);

I'm not a huge fan of such short variable names. Its difficult to figure out what it is. I would before something that explains what this is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 This e/h/b business is historic. I've refactored this function to aid readability.

lib/nano.js Outdated
@@ -68,6 +67,89 @@ module.exports = exports = nano = function dbScope(cfg) {
}
return str;
}
var responseHandler = function(req, opts, resolve, reject, callback) {

return function(e, h, b) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You do a fiew checks lower to check if h exists. You can do something like this instead:

return function (e, h = {}, b) {

That way h will default to an empty header. Also could you rename it to header because I think that is what it is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 h is actually the response!! h is from the original code. I'm refactoring with sensible names!

lib/nano.js Outdated
rh.uri = req.uri;
if (e) {
log({err: 'socket', body: b, headers: rh});
var ret_e = errs.merge(e, {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we using camel case so this should be const retE or `returnError

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

lib/nano.js Outdated
}, function(err, body, headers) {
if (err) {
if (callback) {
callback(err);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would there be a case you want the body and headers for an error as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point 👍

return view(ddoc, viewName, {type: 'view', stream: true}, qs, callback);
}


// geocouch
function viewSpatial(ddoc, viewName, qs, callback) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might have ton consider dropping support for Geocouch. It doesn't work with CouchDb 2.x

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll create an issue to track this.#103

lib/nano.js Outdated
@@ -738,6 +825,25 @@ module.exports = exports = nano = function dbScope(cfg) {
}, callback);
}

function insertAttAsStream(docName, attName, att, contentType, qs, callback) {
if (typeof qs === 'function') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have this check everywhere. You could consider a function that does this everywhere for us.

   const {qs, callback} = getCallback(qs0, callback0);

  function getCallback (opts, callback) {
        if (typeof opts === 'function') {
        callback = opts;
         qs = {};
     }

      return {
        qs,
        callback
     };
    }

It might make things a little neater. I know we do something similar in PouchDB.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 done


```js
// Nano 7
var db = nano.db.use('mydb')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets promote good practice and use const here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

```js
// Nano 7
var db = nano.db.use('mydb')
var x = db.get('mydoc')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could maybe make this const x = await db.get('mydoc'); To show its a promise and we could use async/await

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@@ -3,11 +3,12 @@
"node": true,
"strict": true,
"smarttabs": true,
"maxlen": 80,
"maxlen": 100,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

naughty!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😊

Copy link
Member

@janl janl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nicely done @glynnbird! I gave it a very shot spin in a local project and it seems to work just fine.

```js
// Nano 7
const db = nano.db.use('mydb')
const x = await db.get('mydoc')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

x is a what now? (just so the examples have parity)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

})
```

The Promise style of code lets you code sequences of asynchronou calls at the same level of indentation (the callback style forces you to indent further to the right with each call!):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/asynchrnonou/asynchrnonous/

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

README.md Outdated
})
```

or in the `async/await` style:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nickpick, but should we make this the first example and show raw promises second? The code is just so much nicer :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@janl
Copy link
Member

janl commented Jul 23, 2018

@glynnbird how does this relate to #88?

@glynnbird
Copy link
Contributor Author

@janl The idea of #88 was to make it so that Promises could be switched on with a config flag. A neat idea, but it plays havoc when you're using it with TypeScript which likes functions to return a fixed type, so instead I wrote-up issue #98 to make Promises the default - to mirror the expectations of the modern JS/TS user. That way the return values would always be Promises, which keeps TS happy and added some extra functions which return streams to the streamy stuff. So, in short, #88 was the inspiration, but it slightly different approach won the day.

@janl
Copy link
Member

janl commented Jul 24, 2018

@glynnbird thanks for the context!

Copy link
Member

@garrensmith garrensmith left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is awesome. Thanks @glynnbird huge +1


return function(err, response = { statusCode: 500 }, body = '') {
let parsed;
const responseHeaders = Object.assign({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks this is much clearer to read.

@glynnbird glynnbird merged commit 8d71498 into master Jul 24, 2018
@glynnbird glynnbird deleted the issue98 branch July 24, 2018 11:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants