I've been trying to use gm with Bluebird like this:
var gm = require('gm');
var bluebird = require('bluebird');
gm = bluebird.promisifyAll(gm);
But then, when I try something like this:
gm(req.file.buffer)
.crop(tWidth, tHeight, tWidth/2, tHeight/2)
.gravity('Center')
.toBuffer()
.then(...)
I get this error:
gm().toBuffer() expects a callback.
If I only promisify the buffer method:
bluebird.promisify(gm(req.file.buffer)
.crop(tWidth, tHeight, tWidth/2, tHeight/2)
.gravity('Center')
.toBuffer)()
.then(buff => {...})
I get this error:
TypeError: this.stream is not a function
6:36:21 AM web.1 | at toBuffer (/Users/danielrvt/IdeaProjects/twe-backend/node_modules/gm/lib/command.js:162:17)
If I don't use promises, it works just fine.
That's not really how you're supposed to use the gm module as it is designed to provide a factory gm() and a number of cascadeable mutator functions like .resize().
Bluebird's .promisifyAll() only works on functions that accept a callback, which the majority of gm's functions do not.
If you wish to use gm with promises, you'll need to "promisify" it yourself, by wrapping your call as
function mutateAndSave() {
return new Promise( function(resolve,reject) {
try {
gm(image)
.doSomething()
.write(outputPath, function(err) {
if(err) {
throw(err);
}
resolve();
});
}
catch (err) {
reject(err);
}
});
}
After which, you can
mutateAndSave()
.then(...)
.catch(...);
UPDATE
Here are two ways to do what you want, but...
You'll note that both are a lot more complicated than just using gm as it is intended. ;)
Here's a way to do what you want with an event state machine.
const gm = requre('gm');
const EventEmitter = require('events');
const input_path = './image.jpg'
const output_path = './newimage.jpg';
const worker = new EventEmitter(); // create an event worker
// define the worker states - note: NO error checking! Muy mal!
const event_states={
start:() => worker.emit('resize',gm(input_path)), // creates a new gm image instance
resize:(img) => worker.emit('crop', img.resize(100,100)), // resizes the image
crop:(img) => worker.emit('write', img.crop(2,2,50,50)), // crops it
write:(img) => { // and writes it to the filesystem
img.write(output_path, err => {
if(err) {
worker.emit('error',err);
return;
}
worker.emit('complete');
});
},
error: (err) => console.error(err.toString()), // report error
complete: () => console.log('complete') // alert on completion
};
// add the states to the worker as event handlers
Object.keys(event_states).forEach(k => worker.on(k, event_states[k]));
// and fire it up...
worker.emit('start');
Or if you really, REALLY want to use Promises...
const writer = function (img) {
return new Promise( (resolve, reject) => {
img.write(output_path,err => {
if(err) {
reject(err);
return;
}
resolve(true);
});
});
};
const reader = function (input_path) {
return new Promise( (resolve,reject) => {
let img;
try {
img = gm(input_path);
}
catch (err) {
reject(err);
return;
}
resolve(img);
});
};
reader('./image.jpg')
.then( img => { return img.resize(100,100) }) // the return here is for illustration only
.then( img => img.crop(2,2,50,50)) // since this does exactly the same thing with less typing!
.then( writer )
.then( res => console.log('complete'))
.catch( err => console.error(err.toString()));
Again, lots more typing and complexity all to use the newest "shiny" thing. ;)
Related
My download code relies on listening on listening for events to determine when to fire callbacks, and whether the promise it's in should be resolved or rejected:
async function downloadMtgJsonZip() {
const path = Path.resolve(__dirname, 'resources', fileName);
const writer = Fs.createWriteStream(path);
console.info('...connecting...');
const { data, headers } = await axios({
url,
method: 'GET',
responseType: 'stream',
});
return new Promise((resolve, reject) => {
const timeout = 20000;
const timer = setTimeout(() => {
console.log('timed out'); // debug log
writer.close();
reject(new Error(`Promise timed out after ${timeout} ms`));
}, timeout);
let error = null;
const totalLength = headers['content-length'];
const progressBar = getProgressBar(totalLength);
console.info('...starting download...');
// set up data and writer listeners
data.on('data', (chunk) => progressBar.tick(chunk.length));
data.on('error', (err) => { // added this to see if it would be triggered - it is not
console.log(`did a data error: ${error}`);
error = err;
clearTimeout(timer);
writer.close();
reject(err);
});
writer.on('error', (err) => {
console.log(`did a writer error: ${error}`);
error = err;
clearTimeout(timer);
writer.close();
reject(err);
});
writer.on('close', () => {
const now = new Date();
console.log(`close called: ${now}`);
console.log(`error is: ${error}`);
console.info(
`Completed in ${(now.getTime() - progressBar.start) / 1000} seconds`,
);
clearTimeout(timer);
console.log(`time cleared: ${timer}`);
if (!error) resolve(true);
// no need to call the reject here, as it will have been called in the
// 'error' stream;
});
// finally call data.pipe with our writer
data.pipe(writer);
});
}
I had some issues writing my tests, but I managed to get something that worked, despite feeling slightly messy, based on this advice:
Here is my test, with the relevant bits of my set up:
describe('fetchData', () => {
let dataChunkFn;
let dataErrorFn;
let dataOnFn;
let writerCloseFn;
let writerErrorFn;
let writerOnFn;
let pipeHandler;
beforeEach(() => {
// I've left all the mocking in place,
// to give an idea of what I've set up
const mockWriterEventHandlers = {};
const mockDataEventHandlers = {};
dataChunkFn = jest.fn((chunk) => mockDataEventHandlers.data(chunk));
dataErrorFn = jest.fn((chunk) => mockDataEventHandlers.data(chunk));
dataOnFn = jest.fn((e, cb) => {
mockDataEventHandlers[e] = cb;
});
writerCloseFn = jest.fn(() => mockWriterEventHandlers.close());
writerErrorFn = jest.fn(() => mockWriterEventHandlers.error());
writerOnFn = jest.fn((e, cb) => {
mockWriterEventHandlers[e] = cb;
});
const getMockData = (pipe) => ({
status: 200,
data: {
pipe,
on: dataOnFn,
},
headers: { 'content-length': 100 },
});
axios.mockImplementationOnce(() => getMockData(pipeHandler));
fs.createWriteStream.mockImplementationOnce(() => ({
on: writerOnFn,
close: writerCloseFn,
}));
jest.spyOn(console, 'info').mockImplementation(() => {});
jest.spyOn(console, 'log').mockImplementation(() => {});
});
it.only('handles errors from the writer', async (done) => {
console.log('writer error');
expect.assertions(1);
pipeHandler = (writer) => writer.emit('error', new Error('bang'));
try {
await downloadMtgJsonZip();
done.fail('ran without error');
} catch (exception) {
// expect(dataErrorFn).toHaveBeenCalled(); // neither of these are called
expect(writerErrorFn).toHaveBeenCalled();
}
});
I would have expected, that when data(pipe) ran, and the writer emitted a new error, it would have triggered at least one of the error listeners.
The code runs as expected, and it even handles the timeout (which I initially set too low), but this last test doesn't run.
As I commented above, neither of the functions above are called, so the expect.assertions(1); code fails the test.
It's possible I need to fundamentally change how I've written the tests, but I'm not sure how I would do that.
Why doesn't that last test pass?
When the code invokes data.pipe(writer), it's running your pipeHandler function defined in the test. This function takes a given writer object and calls writer.emit(...). I believe the issue is that the writer object being passed in is the one mocked out for fs.createWriteStream(), which doesn't have an emit method defined, so nothing is happening in response to that call. It is likely throwing an error, which you may be able to see in your catch block.
I believe what you want is to invoke the handlers saved by the writerOnFn. One way to do so would be to add a property to the object returned by your mock of fs.createWriteStream named emit and define it as a function that invokes the appropriate handler from inside mockWriterEventHandlers. I haven't tested this code but it would look something like the following
const writerEmitFn = (event, arg) => {
mockWriterEventHandlers[event](arg);
}
fs.createWriteStream.mockImplementationOnce(() => ({
on: writerOnFn,
close: writerCloseFn,
emit: writerEmitFn,
}));
My guess is that jest is gobbling up the error.
In order to continue running in the case of exceptions, jest could be guarding against ever having to run try and throw.
You could try expecting an error to have been thrown using jest's API.
I am confused with the use of promise, specifically of its way of data manipulation (passing values from block to block) and exception handling (bubbling up the error). I am trying to learn a right way to use promise and to handle error, something like
Error: A caught error.
at promiseTwo()
at promiseOne()
at subprocess()
at mainprocess()
Here are my two attempts in implementing them:
Attempt 1: Clumsy, deeply nested, and errors are uncaught.
var subprocess = () => {
return new Promise((resolve, reject) => {
promiseOne().then(data1 => {
// Some code with data1, throw some error
promiseTwo().then(data2 => {
// Some code with data1n2, throw some error
promiseThree().then(data3 => {
// Data manipulation with data1, data2, and data3
return resolve(<...>)
}).catch(err3 => { throw err3 })
}.catch(err2n3 => { throw err2n3 }) // >>> ERR: Cannot get err3.
}.catch(err1n2n3 => { return reject(err1n2n3) }) // >>> ERR: Cannot get err3 or err2.
}
}
return new Promise((resolve, reject) => {
subprocess().then(data => {
// TODO
}).catch(allErr => { return reject(allErr) }
}
Attempt 2: Unable to use data from previous promise block.
var subprocess = () => {
return new Promise((resolve, reject) => {
promiseOne()
.then(data1 => {
// Some code with data1, throw some error
return promiseTwo()
})
.then(data2 => {
// Some code with data1n2, throw some error
// >>> ERR: Cannot get data1
return promiseThree()
})
.then(data3 => {
// Data manipulation with data1, data2, and data3
// >>> ERR: Cannot get data1 and data2
return resolve(<...>)
})
.catch(err1n2n3 => {
return reject(err1n2n3)
})
}
}
return new Promise((resolve, reject) => {
subprocess().then(data => {
// Some code, throw some error
}).catch(allErr => { return reject(allErr) }
}
Note: Some of the promise block (i.e. promiseOne, promiseTwo, etc.) are pre-defined so I do not have control over what data they will return. I am sure there are more errors in the attempts (e.g. if returning a function is a right way to do it).
Please help. Thanks.
for this kind of situation, you can combine promises and async-await together.
From the question, it seems we have three promises and one function that executes and handle them.
You can try something like this -
const subProcess = () => {
return new Promise((resolve, reject) => {
// Using IIFE ( You shouldn't put async keyword on promise callbac )
(async () => {
// Use of try catch to handle the errors
try {
await promiseOne()
await promiseTwo()
await promiseThree()
// Additional code if need after them
} catch(err){
// Handle error ( all three promise error will be transferred here )
}
})()
})
}
The above code waits for the promises to execute one by one and also catch error from all three promises if any.
And as #samuei mentioned, you can also use Promise.all() in this.
const subProcess = () => {
return new Promise((resolve, reject) => {
// Using IIFE ( You shouldn't put async keyword on promise callbac )
(async () => {
// Use of try catch to handle the errors
try {
const myPromises = [promiseOne, promiseTwo, promiseThree];
const res = await Promise.all(myPromises);
// Additional code if need after them
} catch(err){
// Handle error ( all three promise error will be transferred here )
}
})()
})
}
And if you don't want to use async-await then you can do something like this as well
const subProcess = () => {
return new Promise((resolve, reject) => {
const myPromises = [];
const myPromises = [promiseOne, promiseTwo, promiseThree];
Promise.all(myPromises)
.then(res => {
// Handle the response
})
.catch(err => {
// Handle the error
})
})
}
It sounds like you're looking for Promise.all, which lets you set a series of promises in motion, then deal with the results when they are all resolved.
I'm tryng to upgrade this code for a better maintenance, this code uploads two images to a server, i know it's possible to get rid of those .catch, by applying async await functions, and try catch blocks, but it's pretty confusing for me, any help will be apreciated.
this._uploadService.makeFileRequest(Global.url + "/upload-image1/" + response.product._id, [], this.filesToUpload1, 'image')
.then((result: Product) => {
this.filesToUpload1 = null;
this._uploadService.makeFileRequest(Global.url + "/upload-image/" + response.product._id, [], this.filesToUpload, 'image')
.then((result: Product) => {
this.filesToUpload = null;
setTimeout( () => this._router.navigate(['/detail', this.saveProduct._id]), 800 );
})
.catch(err => {
console.log(err);
this._router.navigate(['/detail', this.saveProduct._id]);
})
})
.catch(err => {
console.log(err);
this._router.navigate(['/detail', this.saveProduct._id]);
})
I suggest using a pen and paper to draw a block diagram for the logic involved, i.e. which api gets called first, with what kind of data, then which api comes afterwards; also include any logical conditionals through branching.
After that, you should attempt to write something like
const aggregateFunction = async() => {
try {
const someResponse = await callFirstApi(); // return response
await callSecondApi(someResponse); // use the response of the first api for the second api
if (someConditional) {
await callThirdApi(); // response not returned (i.e. when not required)
}
} catch (error) { // catch all errors from all await if they're not within another try-catch
console.log(error);
}
}
This pattern should eliminate all then and catch blocks. If you need more specific error handling for calling say a specific api, wrap function call inside another try-catch block, but everything should still be within the outer try-catch so that all errors will be caught regardless.
this._uploadService.makeFileRequest = function(){
return new Promise(resolve => {
// do logic of file request
resolve(true);
})
}
var waitForTime = function() {
return new Promise(resolve => {
setTimeout( () => {
this._router.navigate(['/detail', this.saveProduct._id]),
resolve(true)
}, 800 );
})
}
var f = async function(){
try {
await this._uploadService.makeFileRequest(Global.url + "/upload-image1/" + response.product._id, [], this.filesToUpload1, 'image');
await this.fileToUpload1 = null;
await this._uploadService.makeFileRequest(Global.url + "/upload-image/" + response.product._id, [], this.filesToUpload, 'image')
await this.fileToUpload = null;
await waitForTime();
}
catch(e) {
// error logic
}
}
if (this.filesToUpload1 && this.filesToUpload) {
f()
}
this might be another cleaner approach with async,await and promise
How can I store the video title as a global variable?
ytdl.getBasicInfo(videoURL, function(err, info) {
console.log(info.title)
});
I've looked through other questions/answers here, but I seem to always print the title successfully to console, but no matter how I define a variable, it is undefined.
Thanks so much for your help!
EDIT:
This is current method I'm trying, as an example found from another user here:
var vidtitle;
function getTitleVideo (videoUrl){
return new Promise ((resolve, reject) => {
ytdl.getBasicInfo (videoUrl, (err, info) => {
resolve (info.title)
})
})
}
vidtitle = getTitleVideo(`https://www.youtube.com/watch?v=${youtube_video_id}`);
getBasicInfo can be used with a callback or a promise.
Using a callback:
ytdl.getBasicInfo(videoURL, (err, info) => {
// log any error
if (err) return console.error(err);
const {title} = info;
/*
* you must put all your code that uses the title here
* if you try to make a global variable it may be undefined when you
* run the rest of the synchronous code
*/
});
The const {title} = info; is a shorthand for const title = info.title;. You can read more about destructuring here.
Using a promise with then:
ytdl.getBasicInfo(videoURL)
.then(({title}) => {
// same as above you can only use the title here
})
// log any error
.catch(console.error);
({title}) => {...} is also destructuring.
Using a promise with await (can only be used in an async function):
(async () => {
try {
const {title} = await ytdl.getBasicInfo(videoURL);
// use title here
} catch (err) {
console.error(err);
}
})();
Explanation
getBasicInfo is an example of an asynchronous function. That is, it takes time for it to execute (it needs to fetch the video info online).
Before ES2015 (ES6), people used callbacks to work with async functions. (Some people also used promise libraries like bluebird, but I won’t go into that here.) The callback function would execute once the async function has completed or has an error.
For example, Node.js’ require('http').get still uses callbacks:
const {get} = require('http');
get('http://example.com', function (res) {
console.log(`The status code was ${res.statusCode}.`;
}).on('error', console.error);
In this example, the callback does not take an error and instead get returns a ClientRequest, which has an error event.
Another example using the deprecated request library (take from the readme):
const request = require('request');
request('http://www.google.com', function (error, response, body) {
console.error('error:', error); // Print the error if one occurred
console.log('statusCode:', response && response.statusCode); // Print the response status code if a response was received
console.log('body:', body); // Print the HTML for the Google homepage.
});
In ES2015, promises were added (as well as arrow functions). For example, you could create functions that return a promise like this:
// resolves after 500 ms
const doSomething = () => new Promise(resolve => setTimeout(resolve, 500));
// resolves with 'Hello, world!' after 500 ms
const getString = () =>
new Promise(resolve => setTimeout(resolve, 500, 'Hello, world!'));
// rejects with the error 'Error!' after 500 ms
const rejectingPromise = () =>
new Promise((resolve, reject) => setTimeout(reject, 500, new Error('Error!')))
// has a 50% chance to resolve and a 50% chance to reject
const mayReject = () => Math.random() > 0.5
? Promise.resolve('it worked!')
: Promise.reject(new Error("it didn't work"));
// using with then and catch
doSomething()
.then(() => {
console.log('done');
return getString();
})
.then(str => {
console.log(str);
return mayReject();
})
.then(str => {
console.log(str)
return rejectingPromise()
})
.catch(console.error);
You don’t have to reject with an Error but it’s good practice to do so.
In ES2016 (ES7), await and async functions were added. await is another way to "unwrap" a promise (pauses execution until the promise is resolved). await is only available in a function that has been marked as async.
// same stuff as before
const doSomething = () => new Promise(resolve => setTimeout(resolve, 500));
const getString = () => new Promise(resolve => setTimeout(resolve, 500, 'Hello, world!'));
const rejectingPromise = () => new Promise((resolve, reject) => setTimeout(reject, 500, new Error('Error!')));
const mayReject = () => Math.random() > 0.5 ? Promise.resolve('it worked!') : Promise.reject(new Error("it didn't work"));
// an async function
async function f() {}
// an async arrow function
const g = async () => {};
// an immediately invoked arrow function expression
// this does the same thing as the previous example with then/catch
(async () => {
try {
await doSomething();
console.log('done');
const str = await getString();
console.log(str);
// you don't have to assign it to a variable:
console.log(await mayReject());
await rejectingPromise();
} catch (err) {
console.error(err);
}
})();
I recommend reading MDN’s article on asynchronous JS.
I came across a very complicated situation. I'll try to keep it as concise as possible.
So I have a code like this in myModule.js:
const lib = require('#third-party/lib');
const myFunction = () => {
const client = lib.createClient('foo');
return new Promise((resolve, reject) => {
client.on('error', (err) => reject(err));
client.on('success', () => {
client.as(param1).post(param2, param3, (err, data) => {
if (err) reject(err);
// Some important logical processing of data
resolve(data);
});
});
});
}
module.exports = { myFunction };
There are a few things I am able to mock, like: createClient.
What I am not able to mock is the event part I don't even know how to do this. And the .as().post() part.
Here's how my jest test looks like:
const myModule = require('./myModule');
const mockData = require('./mockData');
describe('myFunction', () => {
it('Should resolve promise when lib calls success event', async () => {
try {
const myData = await myModule.myFunction();
expect(myData).toMatchObject(mockData.resolvedData);
} catch (err) {
expect(err).toBeNull();
}
})
});
Any help, much Appreciated.
I tried to find similar questions but at this point, my mind has just stopped working...
Please let me know if you need any more details.
Here’s what you need to do:
// EventEmitter is here to rescue you
const events = require("events");
// Mock the third party library
const lib = require("#third-party/lib");
lib.createClient.mockImplementationOnce(params => {
const self = new events.EventEmitter();
self.as = jest.fn().mockImplementation(() => {
// Since we're calling post on the same object.
return self;
});
self.post = jest.fn().mockImplementation((arg1, _cb) => {
// Can have a conditional check for arg 1 if so desird
_cb(null, { data : "foo" });
});
// Finally call the required event with delay.
// Don't know if the delay is necessary or not.
setInterval(() => {
self.emit("success");
}, 10);
return self;
}).mockImplementationOnce(params => {
const self = new events.EventEmitter();
// Can also simulate event based error like so:
setInterval(() => {
self.emit("error", {message: "something went wrong."});
}, 10);
return self;
}).mockImplementationOnce(params => {
const self = new events.EventEmitter();
self.as = jest.fn().mockImplementation(() => {
return self;
});
self.post = jest.fn().mockImplementation((arg1, _cb) => {
// for negative callback in post I did:
_cb({mesage: "Something went wrong"}, null);
});
setInterval(() => {
self.emit("success");
}, 10);
return self;
});
This is only the mock object that you need to put in your test.js file.
Not sure if this code will work as is, although won’t require a lot of debugging.
If you just want to positive scenario, remove the second mockImplementationOnce and replace the first mockImplementationOnce with just mockImplementation.