Context: I've got a WebApp (frontend: Vue3, backend: node.js + express) with a single vue, and a button. When clicking on a button it triggers a script in the backend, downloading an image, and storing it locally. This image should be rendered on the Vue side.
My Issue: Everything seems to work, except that Vue is not displaying the image completely. I only have a small share of the file displayed.
Backend Controller (images.controller.js
):
const fs = require('fs');
const path = require('path');
const axios = require('axios');
const { v4: uuidv4 } = require('uuid');
const downloadImage = async(fileUrl, fileName) => {
let isDownloaded = false
const downloadFolder = './images';
const localFilePath = path.resolve(__dirname, downloadFolder, fileName);
try {
const response = await axios({
method: 'GET',
url: fileUrl,
responseType: 'stream',
});
if(response.status === 200){
isDownloaded = true;
await response.data.pipe(fs.createWriteStream(localFilePath));
}
} catch (error) {
console.log(error)
}
return isDownloaded;
}
const readImage = async(req, res) => {
try {
const fileName = `${uuidv4()}.jpeg`;
const fileUrl = 'https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/dog-puppy-on-garden-royalty-free-image-1586966191.jpg';
const isDownloaded = await downloadImage(fileUrl, fileName);
if(isDownloaded){
const img = fs.readFile(__dirname + '/images/' + fileName, async(err, data) => {
var contenType = 'image/jpeg';
var base64 = await Buffer.from(data).toString('base64');
base64 = 'data:image/jpeg;base64,' + base64;
res.status(200).send(base64)
})
}
} catch (error) {
console.log(error)
}
}
module.exports = {
readImage: readImage,
}
Frontend side, my Home.vue
:
<template>
<div class="home">
<button @click="readImage">TEST</button>
<div v-if="img">
<img :src="img">
</div>
</div>
</template>
<script>
import ImagesService from '../services/images.service'
export default {
name: 'Home',
data(){
return {
img: ''
}
},
methods: {
async readImage(){
const temp = await ImagesService.readImages({name: 'dog.jpeg'})
this.img = temp.data
}
},
}
</script>
images.service.js
:
import api from '../../http-common';
export default new class ImagesService {
//Read Screenshot
readImages(imageName){
return api.post('read/image', imageName)
}
}
http-common.js:
import axios from 'axios'
export default axios.create({
baseURL: 'http://localhost:3003/api',
headers: {
"Content-type": "application/json",
}
})
it's because stream is not a promise: image is read and downloaded before the stream is finished saving it
Try wrapping data.pipe
in a promise and resolve when the saving is done:
const downloadImage = (fileUrl, fileName) => {
return new Promise(async(resolve, reject) => {
let isDownloaded = false
const downloadFolder = './images';
const localFilePath = path.resolve(__dirname, downloadFolder, fileName);
const imgStream = fs.createWriteStream(localFilePath);
try {
const response = await axios({
method: 'GET',
url: fileUrl,
responseType: 'stream',
});
if (response.status === 200) {
response.data.pipe(imgStream);
}
} catch (error) {
console.log(error)
reject(error);
}
imgStream.on('finish', () => {
resolve(true);
});
})
};