-1

I'm trying to assign a value to myImage. When I look at the js target file myImage doesn't even exist. Obviously this throws an error. How do I preserve the scope of this within typescript classes?

What I'm trying to do here is load an image using the Jimp library. Then have a reference to that loaded image to perform operations on, for example resize is a method of a loaded image inside Jimp, so I'd like to be able to call i.myImage.resize(100,100).

"use strict";
var Promise = require('bluebird');
var Jimp = require("jimp/jimp");


class Image{
    public myImage:any;

    constructor(newImage:string){

         Promise.all([new Jimp(newImage)]).then(function(img){
            this.myImage = img;

         }).catch(function(e){
             console.log(e);
         })
       }
    }

var i = new Image('./jd.jpg');

console.log(i.myImage)

The output:

undefined
[TypeError: Cannot set property 'myImage' of undefined]

With Callbacks :

var Jimp = require("jimp/jimp");


class Image {

    public myImage: any;

    constructor(newImage: string, typeOfImage: string) {
        var self = this;
        new Jimp(newImage, function(e,img) {
           self.myImage = img;
        });
     }

}

var i = new Image('./jd.jpg');

console.log(i.myImage) // outputs undefined
Jonathan Eustace
  • 2,469
  • 12
  • 31
  • 54
  • Promise callbacks don't retain values of `this` from a prior context. If you want to access `this`, then save it to a variable before your `Promise.all()`. Also, unless `new Jimp(newImage)` returns a promise (which it does not), then there is no point in using `Promise.all()` where you are using it. Promises don't have any magic powers to know when some async operation is done. – jfriend00 Sep 23 '15 at 17:25
  • You should probably back up several steps and describe what you're trying to do with the constructor so folks can help you with a higher level solution because you appear to have several things wrong in that code beyond just the `this` issue. – jfriend00 Sep 23 '15 at 17:27
  • I just added the complete code with the output. – Jonathan Eustace Sep 23 '15 at 23:03

1 Answers1

1

The Jimp constructor does not return a promise so your use of Promise.all() here is likely not doing what you want it to do. As some people get confused by this, promises do not have any magic powers to somehow know when the Jimp object has finished loading it's image so if that's what you're trying to do here, it will not work that way. The Jimp constructor returns a Jimp object, not a promise.

I don't see any particular reason to not just use the Jimp callback that you pass into the constructor and make your code work like this:

var Jimp = require("jimp/jimp");

class Image {
    private myImage: any;

    constructor(newImage: string, typeOfImage: string) {
        var self = this;
        new Jimp(newImage, function(img) {
           self.myImage = img;
           // you can use the img here
        });
        // you cannot reliably use the img here
     }
}

But, doing it this way looks like it still leaves all sorts of loose ends because the outside world has no way of knowing when the img has finished loading and you aren't saving the Jimp object reference itself so you can't use any of the Jimp methods on this image. As I said in my comments, if you share the bigger picture for what you're trying to do here, then we can more likely help you with a more complete solution to your problem.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • @Jthomps - You can only use Bluebird's promisify on async functions that follow the node.js calling async convention where the last argument is a callback function that gets called with two arguments, the first one an error value and the second one the value. The results of `require("jimp")` is a constructor function that has to be used with `new` that does NOT follow that calling convention. Thus, you can't use `.promisify()` on it. As I've now said multiple places, if you describe what problem you're really trying to solve, we can probably help you find a good way to solve it. – jfriend00 Sep 23 '15 at 23:03
  • Thank you, however I just switched to callbacks... I'm still having an issue accessing myImage. It comes back as undefined. – Jonathan Eustace Sep 23 '15 at 23:14
  • @Jthomps - Change this `var Jimp = Promise.promisify(require("jimp/jimp")); ` to this: `var Jimp = require("jimp/jimp");` to have a chance at making the callback version work. And, you still have to fix the `this` issue like I showed in my answer. Sorry, but I'm not going to spend any more time on this until you tell me what you're really trying to accomplish (and please edit your question to include that info). – jfriend00 Sep 23 '15 at 23:15
  • I just made the edits, I'm still getting an undefined. – Jonathan Eustace Sep 23 '15 at 23:22
  • @Jthomps - You're trying to wrap another object around the Jimp object and then using the resulting outer object in a synchronous fashion, but you can't do that. I'd suggest you stop trying to wrap something else around it and just use the Jimp object just like it shows in the Jimp documentation. You can't use the actual img until the callback is called that is passed into the constructor. Sorry, but this is far too frustrating for me because you won't show what you really want to do (e.g. like what you want to do with the image). ONLY THEN could we help with the right overall structure. – jfriend00 Sep 23 '15 at 23:27
  • What I'm trying to do is to figure out a way that I can program in javascript as close to OOP as possible. Today was my first day with TypeScript. I've been using promises and generators using bluebird up until now, but I was hoping that I could push it a level further to get a more classic OOP style going on. What I was trying to do to the image was arbitrary, however, in short I just wanted to have a method variable that contained a Jimp object that I could perform operations on. I appreciate your time. :) – Jonathan Eustace Sep 24 '15 at 04:07
  • @Jthomps - well a critical part of the design is how you're going to know from outside your wrapper object, when the image inside is valid. You can't really do anything with your wrapper without knowing that. But, your design does not take that into account. That's why it seems like you need to fix the design first to take that into account and then see what coding problems you have with a fixed design. – jfriend00 Sep 24 '15 at 04:09
  • @Jthomps - this timing issue is the crux of your issue with `console.log(i.myImage)`. You are trying to run that BEFORE the image has finished loading, but you have to create a wrapper that has an inherently asynchronous design which you have not. It uses async stuff internal to the object implementation, but tries (unsuccessfully) to hide that from the outside world. You can't hide async. – jfriend00 Sep 24 '15 at 04:16