Joseph Fraley Joseph Fraley - 17 days ago 6
Node.js Question

Why can't I use Promise.resolve with an osmosis instance?

I am trying to understand why these

console.log
statements behave differently. I expect them to behave the same:

Using Node 7. Consider the following cases:

1. Promise.resolve(object)

Promise.resolve
handles objects as I'd expect:

Promise.resolve({ a: `hello` }).then(console.log) // { a:'hello' }


2. Directly
console.log
a class instance from a library.


If I store an Osmosis instance I can console.log it:

const osmosis = require(`osmosis`)
console.log(new osmosis.get(url))
/* { prev:
{ instance: Osmosis:1,
name: 'get',
args: [ 'http://www.google.com', , ],
getURL: [Function: getURLArg],
url: 'http://www.google.com',
params: undefined,
cb: [Function: Get],
next: [Circular] } }
*/


3. Promise.resolve(class instance)

But if I try to resolve an Osmosis instance I don't see parity:

Promise.resolve(new osmosis.get(url)).then(console.log) // nothing


What's going on here? Am I misunderstanding something about
Promise.resolve()
...? Or
console.log
?

Why doesn't [3] log the same as [2], given the behavior in [1]?



Context: I don't think my immediate practical goals matter to answering this question. But here you go just in case. I don't see how anything about the library itself should affect the output of the final example. Here's the docs on that
new osmosis.get()
though: http://rchipka.github.io/node-osmosis/Osmosis.html#toc1__anchor

new osmosis.get(url)
doesn't perform an async http request. It instantiates an instance of the scraper, which can be built up with a set of declarative instructions and told to "run" at some arbitrary later time.

I want to be able to build this set of instructions in a promise chain for several reasons.

The main one is that it would be the easiest way to break up the instruction definitions into different functions that are easier to test and understand. e.g. instead of
osmosis.get(url).set({some stuff}).find(@something)
, I'd like to:

function defineSearch(instance){
return instance.set({some stuff})
}

function definePath(instance) {
return instance.find(@something)
}

Promise.resolve(new osmosis.get(url))
.then(defineSearch)
.then(definePath)
.then(instance => instance.run())

Answer

The documentation is horrible and the uses techniques are rather unconventional. What new osmosis.get(url) returns is not an Osmosis instance but a Command one. And those do have a then method.

When you pass something to Promise.resolve, it is tested for being a thenable or not, and if it looks like a promise it is tried to be assimilated: a callback is passed into the then method which will resolve the new promise.

So when you do Promise.resolve(new osmosis.get(url)), you get back an unresolved promise that will fulfill when the then callback is called (which happens when you run the command). In your case, it never does.

The solution to your particular problem is not to use promises at all (since you're not doing anything asynchronous):

definePath(defineSearch(new osmosis.get(url))).run())

but you probably should also report a bug that Commands look like promises with being properly thenable, which breaks a lot of things in ES6.