0

I try to promisify SFTPWrapper and hit this problem. I am not sure if it happens to other object or not.

So if I just promisify one SFTPWrapper function, say readdir, bluebird will have unhandledRejection : "Cannot read property 'readdir' of undefined" error. I tried util.promisify, the same error too.

But if promisifyAll(SFTPWrapper) and it work as expected. But why is that ?

---- update -----

The code I use,

var Client = require('ssh2').Client
var conn = new Client()
conn.on('ready', function() {
    conn.sftp(async function(err, sftp) {
        if (err) throw err
        try {
          // promisify will have Cannot read property 'readdir' of undefined error
          // both bluebird and util have the error

          //let readdirAsync = Promise.promisify(sftp.readdir)
          let readdirAsync = util.promisify(sftp.readdir)
          list = await readdirAsync(remotePathToList)

         // Promise.promisifyAll(sftp) work
         const sftp2 = Promise.promisifyAll(sftp)
         let list = await sftp2.readdirAsync(toRead)
Qiulang
  • 10,295
  • 11
  • 80
  • 129
  • You will have to show us exactly what promisify code you're using that doesn't work for us to help you further. Please show the actual code for both the promisify operation and the use of the promisified function. In general, questions about code need to include the actual code you're having a problem with (at least that's the best way to assure that we can help you quickly without lots of further questions). – jfriend00 Oct 31 '18 at 05:04
  • Hi I have shew the code, it is quite simple actually. – Qiulang Oct 31 '18 at 05:19

1 Answers1

2

You don't show exactly where that error "Cannot read property 'readdir' of undefined error" occurs. If it occurs on this line of code:

let readdirAsync = util.promisify(sftp.readdir);

Then, the problem is that sftp is undefined. But, it doesn't appear that that is exactly the issue because you say that const sftp2 = Promise.promisifyAll(sftp) does work.

So, I'm guessing that the problem occurs when you try to use readdirAsync. If that's the case, then it's probably because you lost the sftp parent when you did let readdirAsync = util.promisify(sftp.readdir) and that is somehow important to the readdir method implementation. If that is indeed the case, then you can do this:

let readdirAsync = util.promisify(sftp.readdir).bind(sftp);

To make sure the parent object stays bound to the method. Because Bluebird's .promisifyAll() puts the new methods on the original object and you call them as methods on the object, not as plain functions, this binding to the original object happens automatically when you call them as sftp2.readdirAsync(). You could do that also as in:

sftp.readdirAsync = util.promisify(sftp.readdir);

And, then you could call sftp.readdirAsync(...).then(...).catch(...) and they'd be bound appropriately to the sftp object.

P.S. Using if (err) throw err inside an async callback is NEVER good error handling. You literally should never write that line of code inside an async callback unless the caller of that async callback has an explicit handler for that exception (which is usually not the case). All it does is throw an exception into async oblivion with no opportunity for actual error handling anywhere else in your code.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • It is exactly what you said!! util.promisify(sftp.readdir).bind(sftp) or sftp.readdirAsync solve the problem! Thanks!!! – Qiulang Oct 31 '18 at 06:23
  • @Qiulang - This happens when the host method needs to refer to `this` (it's parent object) in order to do its job. Sometimes that is the case (as in the `sftp` example and sometimes that is not the case as in the `fs` module. It all depends upon the implementation of the method itself and whether it is using local instance data or not. – jfriend00 Oct 31 '18 at 06:31
  • OK so it is not a bug of SFTPWrapper or promisify, just a case to be careful when using promisify. Thanks again. It has been bugging me for several days! – Qiulang Oct 31 '18 at 06:33
  • @Qiulang - Yep, just a generic Javascript issue with taking an external reference to a method that needs it's host object instance data. That's what `.bind()` is for. See [this within ES6 class method](https://stackoverflow.com/questions/36489579/this-within-es6-class-method/36489599#36489599) and [method doesn't work when passed to another function](https://stackoverflow.com/questions/41798995/new-object-in-constructor-from-class-undefined/41799910#41799910). – jfriend00 Oct 31 '18 at 06:52