Form won't append image correctly

I use an algorithm that turns an image in a Blob through its URI, then adds it to a Form and sends it to the server. Until yesterday everything was working normally, but it just stopped working. I tried to revert to a previous edition, but error continues even in that version! The problem is not on the server because all other data is received normally and nothing has been changed on that side. I really can’t understand what happened.

My code is like this:

// Read all images and append to a form data
syncImages() {
	let formData = new FormData();
	this.images.forEach((value, index, array) => {
		this.dataURItoBlob(value.filePath).then(returnedBlob => {
			let imageBlob = new Blob([returnedBlob], {type: "application/octet-stream"});
			formData.append('image', imageBlob, value.name);
			formData.append('pointId', value.pointId); 
		}).then(() => {
			// Send to DB only in last iteration
			if (index === array.length - 1) { 
				this.sendImagesToDB(formData);
			}				
		});
	});	
}

If I log the returnedBlob or imageBlob here I can see the Blob file with its correct size, but if I log the formData it returns empty! The sendImagesToDB is just a HTTP POST that sends the formData to DB. I’m not receiving any errors. The maddening part is that this same code was working perfectly yesterday. I modified the response from my server to simply display what was being received (var_dump), and what I am getting is just the name of the image:

array(1) {
  ["image"]=>
  array(5) {
    ["name"]=>
    string(17) "1571096712173.jpg"
    ["type"]=>
    string(0) ""
    ["tmp_name"]=>
    string(0) ""
    ["error"]=>
    int(1)
    ["size"]=>
    int(0)
  }
}

Another particularity that I noticed is that before I could see these answers by the console itself, in the “Network” tab (I use Google Dev Tools for this), now I can only see the response through what is returned by the observable HTTP POST.

The most important influence on my programming style is Usenet, and specifically comp.lang.c. The oldhats in that group used to harp on two concepts, which were sort of related: portability and “undefined behavior”. Whenever I see “sometime it works, sometimes it doesn’t” or “it stopped working all of a sudden” or “it works except on a Galactron Mark 3”, my mind goes to “relying on undefined behavior”, and I think that’s what we have here.

A metaphor I use for thinking about Promises is the proverbial “message in a bottle”. You are firing off five messages in five different bottles, and inadvertently relying on the (very tenuous) hope that the last one you threw in the ocean is going to arrive last on the distant shore.

Instead, use Promise.all to corral them all into a single crate and only toss the crate in the water after all the bottles are ready. You could create a second interface to go with whatever the existing definition of “image” has here, or just tack on another field. I’ll go with the second approach here for brevity, although memory leaks and ownership confusion may lurk.

I’m breaking this up into littler sub-functions for clarity. This tactic helps me because when each function does one small thing, it prevents me from trying to get cute with bigger-picture things like if (index === array.length - 1). Note that the first word of each function that deals with futures is return: this is another tactic that tends to cut off avenues in which bugs tend to lurk.

interface TaggedImage {
  filePath: string;
  name: string;
  pointId: string;
  blob?: Blob;
}

// warning: modifies argument in place
blobify(images: TaggedImage[]): Promise<TaggedImage[]> {
  return Promise.all(images.map(img => {
    return this.dataURItoBlob(img.filePath).then(rb => {
      img.blob = new Blob([rb], {type: "application/octet-stream"});
      return img;
    });
  });
)

formify(images: TaggedImage[]): Promise<FormData> {
  return this.blobify(this.images).then(imgs => {
    let rv = new FormData();
    imgs.forEach(img => {
      rv.append("image", img.blob, img.name);
      rv.append("pointId", img.pointId);
    }
    return rv;
  });
}

sendImages(): Promise<void> {
  return this.formify(this.images).then(fd => {
    return this.sendImagesToDB(fd);
  });
} 
1 Like

Thank you very much for your help, this is the first time I use Interfaces, very interesting its implementation. Indeed this new method, besides being much more organized, is easier to understand what is happening.

Unfortunately this did not solve the initial problem, which was caused by my lack of attention. On the server I kept getting just the name of the image. Looking back at what I was getting, I realized this [" error "] => int (1) from the var_dump () of the server. Checking this error in the PHP manual I realized that it turned out to be a PHP main file configuration error, which was limiting the upload file size.

Nevertheless this post ended up yielding good teachings and I thank you once again for that!

1 Like