Closed kuraga closed 8 years ago
I presume this is wrapped in a generator and probably run with vo
or co
or something similar?
Nightmare (partially) implements promises, as you have already discovered. A very condensed, hand-waving description: the .then()
call is called by yield
, the result of which is returned to exists
. Without yield
, exists
will be the Nightmare instance itself.
Your example, rewritten with the promise implementation for what it's worth:
page.exists('h1.title')
.then(function(exists){
assert(exists);
});
As for your other question about getting a Promise
instance without calling .then()
, I'm curious why you would want to do that. You could probably use the Nightmare instance itself, but that might have side effects you don't want. What are you trying to accomplish?
the
.then()
call is called byyield
That's logical but I don't see this this behavior in articles about generators... About .next()
call only not .then()
. Who does call .then()
?
I want to understand: 1) the schema (of Nightmare and generators), 2) if I can use Nightmare with vanilla NodeJS and without Mocha.
Thanks!
That's logical but I don't see this this behavior in articles about generators...
My original hand-waving explanation buries almost all of the complexity involved, and frankly, I'm not sure I have a great grasp on it myself. I'll give it a try, at least.
About .next() call only not .then(). Who does call .then()?
This requires a longer explanation.
I think it's important to first talk about iterators, the return type of generators. They are comprised of two parts: a done
member, which is true if the iterator is past the end of the iterated sequence, and a value
member, which can be any value. With that in mind, let's take a look at an example:
var run = function * () {
yield 'hello';
};
var runner = run();
var result = runner.next();
console.log(result);
result = runner.next();
console.log(result);
...which will output:
{ value: 'hello', done: false }
{ value: undefined, done: true }
Here, we can see that yield
returns "hello"
and waits for .next()
to be called. When it is, the generator iterates with done
being true
.
You can also yield on promises, although it's not as pretty. Calling .then()
is up to the calling code. The following example is a bit trickier:
var run = function * () {
var promise = new Promise(function(resolve) {
return resolve('hello')
});
var x = yield promise;
x += '!';
return x;
};
var runner = run();
var result = runner.next();
console.dir(result)
result.value.then(function(resolvedValue) {
console.log(resolvedValue);
result = runner.next(resolvedValue + ' world');
console.dir(result);
});
...which will output:
{ value: Promise { 'hello' }, done: false }
hello
{ value: 'hello world!', done: true }
It's a little bit uglier, but not all that different than before: the generator function yields the promise, which the calling code resolves. The calling code then calls .next()
with the resolved value with some additional information, passing that argument back to x
. Finally, x
has "!"
added and returned for the last return value.
co
and .then()
Now, with that background out of the way, I'm going to focus on co
because I think vo
relies on it (via wrapped
) for generators. Additionally, I think co
is the most straightforward way to answer your question.
co
wraps promises up for you so you can execute generator code without intermediate calls to .next()
. It will return the resolved value back to .next()
and ultimately back to what yield
is setting or being passed to (x
in our previous example). You lose the ability to run intermediate code, but gain the ability to run complex generators without having to manage the yield
chain yourself. In your original question, var result = yield page.exists('h1.title')
works because co
handles .then()
for you: since Nightmare is a then
able, co
will resolve the value returned by .exists()
.
If you're interested, I've put together a couple of places in the co
source that might help your understanding:
co
uses next internallyco
promisifies all .next()
valuesco
uses an internal next
function to determine if the generator is complete and to resolve values if complete, and if not, issue a call to .then()
on the value that has been made into a promiseHopefully the above makes sense. Please let me know if you have questions.
if I can use Nightmare with vanilla NodeJS and without Mocha.
Of course! As of Node 4.x, promises are natively supported. This means you can use vanilla javascript to control Nightmare. For completeness, the decision to move to using native promises over something like co
is explained (at length) in #491.
An example on using Nightmare with native promises is provided in the readme.
@rosshinkley big thanks about explanation!
if I can use Nightmare with vanilla NodeJS and without Mocha.
Of course! As of Node 4.x, promises are natively supported.
I meant "without mocha-generators
, mocha
, co
and vo
".
Please note that the examples are using the
mocha-generators
package for Mocha, which enables the support for generators.
Is this description wrong here? Do you mean "which wraps generator function with co
/vo
" instead of "enables the support for generators"? NodeJS 4.2 does support generators (so they are already "enabled") but seems like code doesn't work without mocha-generators
.
@kuraga No problem.
I meant "without mocha-generators, mocha, co and vo".
You don't need co
or vo
to run Nightmare. ES6 promises are native - meaning no library is required - and work fine. You can run Nightmare with callbacks, but it's not directly supported. Maybe I don't understand what the problem is?
Is this description wrong here? Do you mean "which wraps generator function with co/vo" instead of "enables the support for generators"? NodeJS 4.2 does support generators (so they are already "enabled") but seems like code doesn't work without mocha-generators.
I think it's a little misleading. Generators are native to Node 4.x. I think the point the documentation is trying to make is that the tests are written using generators and run using mocha-generators
which I believe works like co
or vo
.
The problem is that yield promise
doesn't call promise.then()
in vanila NodeJS (if I use example with generators but without mocha-generators
. Promises and generators are enabled). Seems like it just yields promise
instead.
The problem is that
yield promise
doesn't callpromise.then()
in vanila NodeJS...
No, it does not. If you use vo
or co
or mocha-generators
, it takes care of that for you. If you wanted to use yield
and .then()
in vanilla JS with no dependencies, you'd have to manage the promise chain yourself. That's the point I was trying to make in this comment.
I also feel like maybe there's a misunderstanding of how Nightmare is intended to be used. You don't need to use yield
. Using generators adds convenience, but isn't strictly necessary. You could use the resolved callback of .then()
to get values from the Nightmare then
able. That's how the example at the top of the readme works.
Backing all the way up to your original example, you could check for/assert for existence without yield or the need for generators. I know I wrote it before, but presented again, consider:
page
.exists('h1.title')
.then(function(exists){
assert(exists);
})
This will take the existing page, queue up an existence check, then run the queue and call back with the result of that existence check. Is this method causing problems? Do you have a more complete example?
No, it does not. If you use vo or co or mocha-generators, it takes care of that for you. If you wanted to use
yield
and.then()
in vanilla JS with no dependencies, you'd have to manage the promise chain yourself.
That's exactly I wanted to hear. Remember my words:
Please note that the examples are using the
mocha-generators
package for Mocha, which enables the support for generators.Is this description wrong here? Do you mean "which wraps generator function with
co
/vo
" instead of "enables the support for generators"?
Let's precise documentation?
Thanks, @rosshinkley !
That's exactly I wanted to hear. Remember my words:
I understand what your point is now, I think. The documentation doesn't explicitly say how and when to use yield
, generators, etc. I don't think the documentation should: Nightmare is designed for use out of the box with plain ES6 promises. It's up to you, the user, to determine what's best for your application's flow control.
I'd be curious to hear if anyone is using ES6 managing promises with yields themselves. I doubt yielding on a promise outside of a flow control library is common, but don't have any evidence to back that assumption.
Let's precise documentation?
We talked about documentation at length in #491 (it's a very, very long read). Ultimately, this is why I started nightmare-examples - it was evident the documentation and usage tripped people up, so adding supplementary documentation and examples here and there to try and clear up some of the common problems made sense. How co
et al interacts with yield
and promises might make for a useful addition.
I believe this issue is resolved. If this is still a problem, feel free to reopen/open another issue.
@rosshinkley thank you for the detailed explanation about how co
works. The references to its internals are great !
Please consider linking that comment in the co
example, as well as Loops and everywhere else vo
/co
appears. Hell, even on the co
official documentation !
Thank you !
Good day!
work but only with
yield
? What's the magic here?Promise
instance without callingNightmare.prototype.then
?Thanks!