JS callback-promise combined pattern : good or bad practice? - javascript

I'm writting some library that will provide asynchronous methods, I want the user to be able to use classic callbacks or modern promises depending on its own preference.
I was coding two versions of each methods by naming them 'myMethod' and 'myMethodPromise', then a thought came accross my mind :
Why not coding a method that combines the two patterns?
One single method with an optional callback argument, if not provided then the method returns a promise instead of calling the callback.
Would it be a good practice?
// Promise-callback combined pattern method
function myAsyncMethod ( callback = null ) {
if(callback) {
var result = "xxx";
// Do something...
callback(result);
} else {
return(new Promise((res, rej) => {
var result = "xxx";
// Do something...
res(result);
}))
}
}
// Usage with callback
myAsyncMethod((result)=>document.getElementById('callbackSpan').innerHTML = result);
// or with promise
myAsyncMethod().then((result) => document.getElementById('promiseSpan').innerHTML = result);
<p>
Result with callback : <span id="callbackSpan"></span>
</p>
<p>
Result with promise : <span id="promiseSpan"></span>
</p>
**

You can follow request-promise module. They have wrape request module inside request-promise. And as only written small config file to assign all promisable methods.
More: https://github.com/request/request-promise/blob/master/lib/rp.js
You can also use node-promisify
to promisify method.
const util = require('util');
const fs = require('fs');
const stat = util.promisify(fs.stat);
stat('.').then((stats) => {
// Do something with `stats`
}).catch((error) => {
// Handle the error.
});
// Request module
Bluebird.config({cancellation: true});
configure({
request: request,
PromiseImpl: Bluebird,
expose: [
'then',
'catch',
'finally',
'cancel',
'promise'
// Would you like to expose more Bluebird methods? Try e.g. `rp(...).promise().tap(...)` first. `.promise()` returns the full-fledged Bluebird promise.
],
constructorMixin: function (resolve, reject, onCancel) {
var self = this;
onCancel(function () {
self.abort();
});
}
});
request.bindCLS = function RP$bindCLS() {
throw new Error('CLS support was dropped. To get it back read: https://github.com/request/request-promise/wiki/Getting-Back-Support-for-Continuation-Local-Storage');
};

Related

Not sure how to handle this in react - multiple promises

So the easiest way to explain this is that I am trying to search your package.json for all your packages, then use the npm registry to see if there are new updates. I have most of that done. Accept for the following for loop:
import request from 'request'
export const findAllUpdates = (packageInfo) => {
for(prop in packageInfo) {
request('https://registry.npmjs.org/' + prop, function(err, resp) {
json = JSON.parse(resp.body);
if (json['dist-tags'].latest !== packageInfo[prop].version) {
// Do something, we are not the same version so we should
// update some counter.
}
});
}
}
Your packageInfo is a object of key=>value which represents either a dependency or a dev dependency from the package.json and the package-lock.json or the yarn.lock
The important part is we look at, in the above function, what you have installed, we then use the registry to get that package info and compare the latest version on the registry to that of what you have installed and then we want, ideally, to update the state of the component with the total count.
how ever the issue is that we have entered callback hell, especially with a for loop, looping over every package, making a request.
I cannot create a variable and store the response in there because it cannot be accessed after the request is made. I cannot see how using events here would work because you might have 40 packages installed as either dep or dev and thats a lot of events to fire.
And finally the proper solution might be to use promises, but the whole concept of promises is yet more callbacks with the .then(() => {}), .catch(() => {}) which puts me back at square one.
The goal is to call this in a component and have the state of that component be updated with the total amount of packages that have to be updated (or at least have new versions)
Any ideas? Am I going about this all wrong?
You're right to be looking at promises.
The first thing is to give yourself a promise-enabled version of request. There's a promisify function in util that takes a Node-callback-style function and returns a promise-enabled version. So:
import util from 'util';
// ...
const prequest = util.promisify(request);
(There's also an npm module, promisify, that can do entire APIs all at once.)
Then, do your requests, gathering the promises in an array (and using then on them to do any post-processing; then returns a new promise, so we're still good), and use Promise.all to wait until they all resolve (or any of them rejects);
So all together:
import request from 'request';
import util from 'util';
const prequest = util.promisify(request);
export const findAllUpdates = (packageInfo) => {
const updates = []
for (const prop in packageInfo) {
updates.push(prequest('https://registry.npmjs.org/' + prop).then(resp => {
const json = JSON.parse(resp.body);
if (json['dist-tags'].latest !== packageInfo[prop].version) {
// Do something, we are not the same version so we should
// update some counter.
}
// Perhaps return something here, it will be the resolution
// for the promise for this request; otherwise, the promise
// will resolve with `undefined`.
}));
}
return Promise.all(updates);
};
That overall function's promise will resolve to an array of the results of each promise in the array (in order), or (again) reject if any of them rejects.
I want to be able to suggest async/await, but it doesn't currently bring much to the table for a bunch of parallel promises. (There's been the occasional talk of await.all to do what Promise.all does, but it hasn't caught on [yet?].)
Having said that, though, if we break things up a bit it is a bit nicer with async/await:
import request from 'request';
import util from 'util';
const prequest = util.promisify(request);
const checkOne = async (prop) => {
const resp = await prequest('https://registry.npmjs.org/' + prop);
const json = JSON.parse(resp.body);
if (json['dist-tags'].latest !== packageInfo[prop].version) {
// Do something, we are not the same version so we should
// update some counter.
}
// Perhaps return something here, it will be the resolution
// for the promise for this request; otherwise, the promise
// will resolve with `undefined`.
};
export const findAllUpdates = (packageInfo) => {
const updates = []
for (const prop in packageInfo) {
updates.push(checkOne(prop);
}
return Promise.all(updates);
};
And of course, if all the properties in packageInfo are "own" properties (not inherited), findAllUpdates gets a lot simpler:
export const findAllUpdates = (packageInfo) => {
return Promise.all(Object.keys(packageInfo).map(checkOne));
};
Side note: I've added a few missing declarations above.
I would recommend using Promise. You can make several async calls, and then wait for them using Promise.all.
If you also return a Promise from your "findAllUpdates" method, then it is easy for the caller to just update its own state when the information is available.
In React that would be something like:
findAllUpdates(..)
.then(result => this.setState({
packagesNeedUpdate: result.filter(p => !p.latest).length
}));
I've created a simple example below that uses promises. It fakes the requests, but otherwise should be pretty accurate.
// Fake requests. Will return version '1.0.0' after one second.
const makeRequest = url => {
return new Promise(resolve => {
setTimeout(() => {
resolve(JSON.stringify({
'dist-tags': {
'latest': '1.0.0'
}
}, null, 2));
}, 1000);
});
}
const findAllUpdates = (packageInfo) => {
return new Promise(resolve => {
const promises = Object.keys(packageInfo)
.map(prop => makeRequest('https://registry.npmjs.org/' + prop).then(JSON.parse));
Promise.all(promises) // Wait for all promises
.then(jsons => {
const results = Object.keys(packageInfo)
.map((prop, i) => {
const latestInNpm = jsons[i]['dist-tags'].latest;
const current = packageInfo[prop].version
return {
prop,
latest: latestInNpm === current
}
});
resolve(results); // Return result to caller
});
});
}
function run() {
console.log('Loading..');
const packages = {
'react': {version: '0.0.1'},
'angular': {version: '1.0.0'},
'ember': {version: '0.5.0'},
'mithril': {version: '0.9.0'}
};
findAllUpdates(packages).then(result => {
const needUpdates = result.filter(p => !p.latest).length;
console.log('Need updates: ', needUpdates);
});
}
run();

Meteor method call returns undefined on the client but not on the server

UPDATE
I just realized something fundamentally wrong in this approach and that nested callbacks can't return something to its parent callback. I came in late in the JS world and come from the Promises era and didn't know this is the problem with callbacks. But I didn't see enough examples for Meteor using promises so I used callbacks instead. However, if this code can be improved I'd appreciate it greatly.
Question
So I'm calling a method from the client using:
Meteor.call('cart.useProfileAddress', {}, (error, address) => {
console.info('Address', address) // this returns undefined on client
})
This is the method in my api/carts/cartsMethod.js
export const useProfileAddress = new ValidatedMethod({
name: 'cart.useProfileAddress',
validate(args) {
//
},
run(args) {
const person = Persons.findOne({'userId': Meteor.userId()});
// If I do the return here I get the address in the browser as defined.
// return person.address
// I'm calling another method under here:
getClosestStore.call({address: person.address}, (error, result) => {
// And another one method call here:
updateCartAddress.call({address: person.address}, (error, result) => {
// So once all the callbacks are done return the address here.
// However the problem is I get `undefined` on the client.
if (!error) {
// console displays something on the Server but is `undefined` on the Client
console.info('Returning Address', person.address)
return person.address
}
})
})
}
})
What could be the problem on the code above? Could it be because I'm trying to get the value from a nested callback?
Also does anyone know how to avoid these nested callbacks? I know how to do it on Node using promises but in Meteor (I'm using 1.4) I'm still clueless.
Methods can run synchronously on server so you do not need to use callback. Result of method will be returned after execution or exception will be thrown if error occurs. Try this:
export const useProfileAddress = new ValidatedMethod({
// ...
run(args) {
const person = Persons.findOne({'userId': Meteor.userId()});
const result1 = getClosestStore.call({address: person.address});
// use result1 if you need to
const result2 = updateCartAddress.call({address: person.address});
// // use result2 if you need to
return person.address;
}
})
This is how I solved my problem using Promise and the new async/await feature of Meteor 1.3+
export const useProfileAddress = new ValidatedMethod({
name: 'cart.useProfileAddress',
validate(args) {
//
},
run(args) {
return ((async () => {
const person = Persons.findOne({'userId': Meteor.userId()});
const storeId = await getClosestStore.callPromise({address: person.address})
const newAddress = await updateCartAddress.callPromise({address: person.address})
return newAddress
})())
}
})
Inside each method I used the didericis:callpromise-mixin so that it will return a promise.

Define fallback catch for promise chain?

I'm working on an application that uses what-wg fetch all over the place. We've defined default fetch middleware and options this way:
export function fetchMiddleware(response) {
return new Promise(resolve => {
resolve(checkStatus(response));
}).then(parseJSON);
}
export const fetchDefaults = {
credentials: 'same-origin',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
};
We use our default middleware / fetch options this way:
fetch('/api/specific/route', fetchDefaults)
.then(fetchMiddleware)
.then(function(data) {
// ... Dispatch case-specific fetch outcome
dispatch(specificRouteResponseReceived(data));
});
We want to add a generic, fallback catch to all fetch usages throughout the application, in other words something like this:
export function fetchGenericCatch(function(error) {
showGenericErrorFlashMessage();
})
fetch('/en/api/user/me/preferences', fetchDefaults)
.then(fetchMiddleware)
.then(function(data) {
dispatch(userPreferencesReceived(data));
})
.catch(fetchGenericCatch);
Lots of code duplication. We'd like a utility function / class which can do all of this for us, e.g. something which would work like this:
genericFetch('/api/specific/route') // bakes in fetchDefaults and fetchMiddleware and fetchGenericCatch
.then(function(data) {
dispatch(userPreferencesReceived(data));
}); // gets generic failure handler for free
genericFetch('/api/specific/route') // bakes in fetchDefaults and fetchMiddleware and fetchGenericCatch
.then(function(data) {
dispatch(userPreferencesReceived(data));
})
.catch(function(error) {
// ...
}); // short-circuits generic error handler with case-specific error handler
The main caveat is that the generic catch must be chained after the case-specific thens / catches.
Any tips on how this might be achieved using whatwg-fetch / ES6 Promises?
Related:
There are similar posts, but they don't seem to address the need for a default catch which runs after all non-default thens and catches:
How to create a Promise with default catch and then handlers
Default behavior if no other functions chained to a promise
Edit 14 Oct:
Possible duplicate: Promises and generic .catch() statements
Having WET code isn't the worst option here, as long as error handler is DRY.
fetch(...)
...
.catch(importedFetchHandler);
It causes no problems and conforms to the behaviour of Bluebird and V8 promises, where unhandled rejection event exists to make sure that no promise are left uncaught.
The most simple way to reach this is to introduce promise-like wrapper for fetch promise:
function CatchyPromiseLike(originalPromise) {
this._promise = originalPromise;
this._catchyPromise = Promise.resolve()
.then(() => this._promise)
.catch((err) => {
console.error('caught', err);
});
// every method but 'constructor' from Promise.prototype
const methods = ['then', 'catch'];
for (const method of methods) {
this[method] = function (...args) {
this._promise = this._promise[method](...args);
return this;
}
}
}
which can be used like
function catchyFetch(...args) {
return new CatchyPromiseLike(fetch(...args));
}
A promise-like like that has natural limitations.
Side effects are discarded if converted to real promise:
Promise.resolve(catchyFetch(...)).then(() => /* won't be caught */);
And it won't play well with asynchronous chain (this is a no-no for all promises):
var promise = catchyFetch(...);
setTimeout(() => {
promise.then(() => /* won't be caught */);
});
A good alternative is Bluebird, no need to invent the wheel there, features is what it is loved for. Local rejection events look like exactly what's needed:
// An important part here,
// only the promises used by catchyFetch should be affected
const CatchyPromise = Bluebird.getNewLibraryCopy();
CatchyPromise.onPossiblyUnhandledRejection((err) => {
console.error('caught', err);
});
function catchyFetch(...args) {
return CatchyPromise.resolve(fetch(...args));
}
Phew, that was easy.
I think the solution is as simple as:
export function genericFetch(url, promise, optionOverrides) {
const fetchOptions = {...fetchDefaults, ...optionOverrides};
return fetch(url, fetchOptions)
.then(fetchMiddleware)
.then(promise)
.catch(function(error) {
showGenericFlashMessage();
});
}
A use case which doesn't need a special error handler can simply use it this way:
genericFetch('/api/url', function(data) {
dispatch(apiResponseReceived(data));
});
A use case which needs a special catch, or a more complex chain, can pass in a full-blown promise:
genericFetch('/api/url', function(response) {
return new Promise(resolve, reject => {
dispatch(apiResponseReceived(data));
}).catch(nonGenericCaseSpecificCatch); // short-circuits default catch
});

Strava-V3 & Javascript es6 generators = not compatible?

I'm using node-strava-v3 with Node 5.7 to retrieve JSON collections from Strava's API. Pretty simple actually, no problem actually.
I just want to take the opportunity to try the ES6 Generators. So far so good I think I get the concept. But in the case of this Node-Strava-V3 API wrapper, the methods never return a value or a promise directly. Only Callbacks are allowed.
Which brings me to the question : how can I insert the ES6 Generators concept into play?
Take this : (this requires access_token you can request on the Strava portal)
var strava = require('strava-v3');
var mainActivity = {id: 504641206};
strava.activities.get(mainActivity, function (err, data){
if(err){ console.log("Strava GET issue: ", err);
/// consume the JSON data here...
};
//rest of the code
but can I use the generators to pause until this "strava.activities.get()" method ends? this method does not return any, no value, no promise.
Thanks for all your suggestions
FJ
You could do this with generators and a library like co, but now we have async functions (ES2017, and can be transpiled for outdated environments).
Tangentially related: Any API that provides callbacks can be promise-ified:
const strava = require('strava-v3');
const mainActivity = {id: 504641206};
new Promise((resolve, reject) => {
strava.activities.get(mainActivity, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
}).then(
result => {
//rest of the code
},
error => {
// handle error
}
);
In fact, that common pattern is codified in util.promisify:
const strava = require("strava-v3");
const promisify = require("util").promisify;
const stravaActivitiesGet = promisify(strava.activities.get);
const mainActivity = {id: 504641206};
stravaActivitiesGet(mainActivity).then(
result => {
//rest of the code
},
error => {
// handle error
}
);
There are multiple libraries that promise-ify a whole API at once (rather than per-function), such as node-promisify.
If we use util.promisify and an async function:
const strava = require("strava-v3");
const promisify = require("util").promisify;
const stravaActivitiesGet = promisify(strava.activities.get);
const mainActivity = {id: 504641206};
(async () => {
try {
const data = await stravaActivitiesGet(mainActivity);
//rest of the code
} catch (error) {
// handle error
}
})();
That mostly makes sense if you're doing other asynchronous things in the logic.

TypeError: Cannot call method 'then' of undefined in Nodejs Promises

Can you please explain what is wrong in this code below.
var promise = fs.readFile(file);
var promise2 = promise.then(function(data){
var base64 = new Buffer(data, 'binary').toString('base64');
res.end("success");
}, function(err){
res.end("fail");
});
Its throwing error as TypeError: Cannot call method 'then' of undefined
readFile doesn't return a promise. The NodeJS by and large predates widespread use of promises and mostly uses simple callbacks instead.
To read the file, you pass in a simple callback, as this example from the documentation shows:
fs.readFile('/etc/passwd', function (err, data) {
if (err) throw err;
console.log(data);
});
There is a promisify-node module available that wraps standard NodeJS modules in a promise-enabled API. Example from its docs:
var promisify = require("promisify-node");
var fs = promisify("fs")
fs.readFile("/etc/passwd").then(function(contents) {
console.log(contents);
});
I should emphasize that I don't know it and haven't used it, so I can't speak to how well it does its job. It appears to use nodegit-promise, a "Bare bones Promises/A+ implementation with synchronous inspection" rather than JavaScript's Promise (which is only fair; it predates JavaScript's Promise by a couple of years).
You have to create an Async function which returns a promise or use a promise library like bluebird.js
Vanilla JS
var promise = readFileAsync();
promise.then( function(result) {
// yay! I got the result.
}, function(error) {
// The promise was rejected with this error.
}
function readFileAsync()
{
var promise = new Promise.Promise();
fs.readFile( "somefile.txt", function( error, data ) {
if ( error ) {
promise.reject( error );
} else {
promise.resolve( data );
}
});
return promise;
}
With BlueBird.js
var Promise = require("bluebird");
var fs = Promise.promisifyAll(require("fs"));
fs.readFileAsync("myfile.json").then(JSON.parse).then(function (json) {
console.log("Successful json");
}).catch(SyntaxError, function (e) {
console.error("file contains invalid json");
}).catch(Promise.OperationalError, function (e) {
console.error("unable to read file, because: ", e.message);
});

Categories

Resources