Skip to content

Promise eval in repl yields inaccessible object #18905

Description

@tomas
  • Version: v8.9.4
  • Platform: Linux x64
  • Subsystem: repl

I'm trying to build a custom repl that yields promises before returning the value to the user, but for some reason I'm unable to "reach" the final value, even though it does appear to be correctly assigned.

This is the code I'm using:

var repl = require('repl'),
    vm   = require('vm');

function foo() {
  return new Promise(function(resolve, reject) {
    resolve({ success: true })
  })
}

function myEval(code, context, file, cb) {
  var res = vm.runInContext(code, context)

  if (!res || typeof res.then != 'function') // Non-thenable response
    return cb(null, res)

  res.then(function(val) {
    cb(null, val)
  }, function(err) {
    cb(err)
  })
}

var replServer = repl.start({
  eval: myEval
})

replServer.context.foo = foo;

And this is what happens:

> res = foo()
{ success: true }
> res.success
undefined
> res.constructor
[Function: Promise]

However, the "real" result does get correctly assigned to _:

> res = foo()
{ success: true }
> _.success
true

Is there something I'm missing?

Thanks!

Activity

  1. added
    replIssues and PRs related to the REPL subsystem.
    questionIssues asking questions about Node.js.
    and removed
    questionIssues asking questions about Node.js.
    on Feb 21, 2018
  2. princejwesley commented on Feb 22, 2018

    @princejwesley
    Contributor

    foo() returns promise in res = foo() expression and therefore, res is a promise instance.
    { success: true } its just a REPL output expression which is specifically handled(with custom eval function) for promise instance.

    > res = foo()
    { success: true }
    > res   <- this should return { success: true } (from custom eval)
    { success: true }

    What you need is something like res = await foo(); which will be available in the future release.

    Closing this ticket, feel free to reopen it.

  3. tomas commented on Feb 23, 2018

    @tomas
    Author

    @princejwesley I see, thanks for the heads up. It does seem a bit confusing though. I mean, if res is actually a Promise instance then why is the repl output showing { success: true }? Why doesn't it show the actual value that it holds?

    Well, I ended up figuring it out anyway. I'm now able to get the "real" promise.then result assigned to the variable, so the repl feels synchronous and provides a much nicer experience.

  4. princejwesley commented on Feb 23, 2018

    @princejwesley
    Contributor

    @tomas Your custom eval function (myEval) handles Promise.then and emits { success: true } as output to terminal.

  5. tomas commented on Feb 23, 2018

    @tomas
    Author

    Is that a question? You can look at my original custom eval function in the OP.

    The one I'm using now is way more complex.

  6. princejwesley commented on Feb 23, 2018

    @princejwesley
    Contributor

    @tomas No, its not a question. Just trying to clear the doubt part. Your custom eval function runs the expression in a given context (res is still promise instance) and decides how to emit the resulting value(res) in the terminal.

  7. tomas commented on Feb 23, 2018

    @tomas
    Author

    OK. I still don't see the point why it works like this. Shouldn't what you see be what you get?

  8. devsnek commented on Feb 23, 2018

    @devsnek
    Member
    > res = foo() // res is set to calling foo which returns an unresolved promise
    // `res = foo()` returns the promise as well, which is then handled with:
    // res.then(function(val) {
    //   cb(null, val)
    //   ...
    
    // which then calls back with the resolve value of the promise as the return value
    { success: true }
    // however res is still set to the promise result of foo(), nothing has changed that yet

    what you want is to parse the expression and perform an assignment to the variable like

    res.then(function(val) {
      eval(`${variable} = val`);
      cb(null, val)
      ...

    in #15566 we use acorn (a javascript syntax parser/lexer/tokenizer) to split the assignment, you can do something similar

  9. tomas commented on Feb 23, 2018

    @tomas
    Author

    @devsnek Thanks for a clearer explanation. The eval trick is actually what I'm doing now.

    // however res is still set to the promise result of foo(), nothing has changed that yet

    That's precisely my point. If res is still the result of foo() (the unresolved promise), then why does the repl show the result of the promised function when inspecting it? It should output something like Promise { ... }, don't you think?

  10. devsnek commented on Feb 23, 2018

    @devsnek
    Member

    no, you call the callback with the resolved value ({ success: true }) not the return value of the evaluated expression (Promise { <pending> })

  11. tomas commented on Feb 23, 2018

    @tomas
    Author

    Ok, thanks, and sorry for the confusion. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    replIssues and PRs related to the REPL subsystem.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions