I'm trying to make an api call using the callback method in request, but I'm new to web development and I'm stuck on async at the moment. I've got the following working, but I want to break the logic out some. For example, here's what is working currently
const request = require('request');
class GetAllData{
constructor(){
this.loadData();
}
// Gets all data from api query that I'll need to build everything else I'll need
data(callback){
request({'url':`https://definitely/a/url.json`, 'json': true }, function (error, response, body) {
callback(body);
});
}
loadData(cmrUrl){
console.log("loadData");
this.data(function(result){ console.log(result.foo.bar)});
}
}
var moreData = new GetAllData();
This works, and I can do the two things I need, which are log some results, and make a small calculation with the results. However, if I want to break this logic out into other functions, I get some errors.
const request = require('request');
class GetAllData{
constructor(){
this.loadData();
// Member variables
this._subsetOne;
this._thingICalculated;
// Function call to print data outside of the async call.
this.printData(this._subsetOne, this._thingICalculated);
}
// Gets all data from api query that I'll need to build everything else I'll need
data(callback){
request({'url':`https://definitely/a/url.json`, 'json': true }, function (error, response, body) {
callback(body);
});
}
loadData(cmrUrl){
console.log("loadData");
// Set a class member variable
// ERROR: 'this' is undefined
this.data(function(result){ this._subsetOne = result.foo.bar)};
// Call a member function, which calculates something, and then sets a member variable.
this.calculateSomething = result;
console.log(result);
};
}
// Function which takes in result from async call, then calculates something.
set calculateSomething(result){
this._thingICalculated = result + 1;
}
printData(x, y){
console.log(x,y);
}
}
var moreData = new GetAllData();
From what I've been reading the issues I'm hitting are pretty common, but I'm still not understanding why this isn't working since the call is asyncronous, and I'm just trying to set a variable, or call a function. I'm assuming there's some way to ask the member variable setting and function call to await the completion of the async request?
Fix attempt one
const request = require('request');
class GetAllData{
constructor(){
this.loadData();
this._subset;
}
// Gets all data from api query that I'll need to build everything else I'll need
data(callback){
request({'url':`https://definitely.a.url/yep.json`, 'json': true }, function (error, response, body) {
callback(body);
});
}
loadData(cmrUrl){
console.log("loadData");
this.data(function(result){ this._subset = result
this.getSubset()}.bind(this));
}
getSubset(){
console.log(this._subset);
}
}
var moreData = new GetAllData();
Subset ends up being undefined.
In the constructor, you have to bind any member method that uses this to itself. So before calling loadData:
this.loadData = this.loadData.bind(this);
Also seems like this.data will get its own scope in loadData, which is why this returns undefined inside that function. Thus you have to bind this.data to this (your class instance) as well.
And do the same for any method that accesses this. The problem is classic JavaScript functions by default have an undefined scope. Arrow functions however automatically inherit the scope of the caller.
Related
I'm absolutely out of idea now and i would like anyone with better idea to help give a concise solution to this. here is my code:
Ok what I'm trying to write here is a program that listen to the load event for fetching data from an api and save it in cache, then clear the cache when the user try to exit the browser. using the opt parameter to set when to load and unload the cache outside the method.
class myc {
// ...
async get(url, opt = {}) {
let js, er, data;
if (opt.load === true) {
window.addEventListener('load', getData);
}
if (opt.unload === true) {
window.addEventListener('beforeunload', removeData);
}
async function getData() {
try {
let r = await fetch(url)
if (!r.ok) {
throw new Error(r.statusText)
} else {
js = await r.json();
}
} catch(e) {
er = e.message;
}
if (js) {
localStorage.setItem(key, js);
data = localStorage.getItem(key);
}
// Main problem here (*)
return {
data : data ? data : null,
error: er
};
}
function removeData(){
localSorage.clear();
}
let res = await getData();
window.removeEventListener('load', getData);
window.removeEventListener('beforeunload, removeData);
return res;
}
}
The line with (*) is the main problem here. So how do i return the required value from that function to then be used in another instance
The short answer: you don't.
Even if you returned a value from an event handler, it would have nowhere to go because you don't have access to the thing that actually called the event handler to be able to grab that return value.
Instead, you simply set the value somewhere in the scope you can grab it later.
Since you are in a class, you could simply set it to this.myValue and then grab it with the same.
The only gotcha with that is you have to make sure your event handler is bound to the proper scope.
You can do that one of two ways. On this line:
window.addEventListener('load', getData);
you can bind getData so it looks like this:
window.addEventListener('load', getData.bind(this));
That way the this inside of getData will refer to your class.
Alternatively, you could switch this line:
async function getData() {
to an arrow function syntax:
const getData = async () => {
which will automatically bind to the current context.
Simply use a global variable that you update in the event handler.
Using a call back function (or simply calling another function with the "return" variable value as parameter) to do some more processing is another way of achieving what you probably want.
Example:
async function getData(){
//event handler doing it's task
//create { data : data ? data : null, error: er } as global var
var returnData = { data : data ? data : null, error: er };
//or
functionToDoMoreProcessing({ data : data ? data : null, error: er });
}
I want to fetch data and have it ready for another function to use as a javaScript object. The problem is that the data is fetched after the program completes. Here is the link to the project: https://github.com/bigbassroller/isomorphic-js/blob/master/src/components/pages/Home/HomeController.js. See code here:
import "babel-polyfill";
import Controller from '../../../lib/controller';
import nunjucks from 'nunjucks';
import fetch from "isomorphic-fetch";
import promise from "es6-promise";
function onClick(e) {
console.log(e.currentTarget);
}
function getData(context) {
let data = {
"name": "Leanne Graham"
}
return data;
}
function fetchData(context) {
return fetch("http://jsonplaceholder.typicode.com/users/1").then(function(response) {
let data = response.json().body;
return data;
});
}
export default class HomeController extends Controller {
index(application, request, reply, callback) {
this.context.cookie.set('random', '_' + (Math.floor(Math.random() * 1000) + 1), { path: '/' });
this.context.data = { random: Math.floor(Math.random() * 1000) + 1 };
callback(null);
}
toString(callback) {
// Works
let context = getData(this.context);
// Doesn't work
// let context = fetchData(this.context);
context.data = this.context.data;
nunjucks.render('components/pages/Home/home.html', context, (err, html) => {
if (err) {
return callback(err, null);
}
callback(null, html);
});
}
attach(el) {
console.log(this.context.data.random);
this.clickHandler = el.addEventListener('click', onClick, false);
}
detach(el) {
el.removeEventListener('click', onClick, false);
}
}
Is it possible to have the data fetched before the the page renders? I am trying to keep things as vanilla as possible, because I am trying to learn as much as possible. I've been stuck for days trying to solve this problem, so I am coming to SO for help, and to help others who have the same problem.
My issue is similar to this issue, https://github.com/reactjs/redux/issues/99 but I am not trying to use redux, would rather use promises instead.
When using async calls you can't have a guarantee of when the call will return (therefore async). Which means that if you want something done after the data is returned the place to do it is inside the "then" clause.
Could you please explain some more on your usecase here?
It is not possible. You'd need to change your program design to work with this. Here is a simple example:
Suppose you have some function foo() that returns a string:
function foo() {
x = fetchSync();
return x;
}
Now suppose you don't have fetchSync() and you're forced to do the work asynchronously to compute the string to return. It is no longer possible for your function to have the string ready to return before the end of the function is reached.
So how do you fix it? You redesign the foo() function to be asynchronous too.
function foo(callback) {
// kick off fetch
fetch(function(response) {
// call callback() with the
// the results when fetch is done
callback(response.json())
});
}
Same example using Promises:
function foo() {
return fetch().then(function(response) {
return response.json();
});
}
Generally most environments that run JavaScript will support asynchronous designs. For example, in Node, a JavaScript program will not finish running if there is callbacks registered that can still be called.
I have a method of rest call using request module which is restRequest() which returns response as promise which is asynchronous method, I have to call this method recursively with different parameters after getting the each results and passing that result to same method.
Example code:
restRequest(url, "POST").then(function(response) {
restRequest(secondUrl, 'GET', response).then(function(response2) {
}):
});
will this works, or any other things are there to solve this one.
I would use the async library for this
Specifically the waterfall
Which would work like
async.waterfall([
function firstRequest(callback) {
restRequest(url, "POST").then(function(response) {
callback(null, response);
});
},
function secondRequest (data, callback) {
restRequest(secondUrl, 'GET', data).then(function(response2) {
callback();
});
}
], function (err, result) {
// Handle err or result
});
Sorry for formatting I'm on mobile.
You can read about how async.waterfall works from the link above.
Your method works but depending on how many requests you have you can end up with quite a deep callback hell
But since you are using promises you can just return your promise chain like
restRequest(url, "POST")
.then(function(resp) {
return restRequest(secondUrl, "GET", resp);
})
.then(function(resp) {
return restRequest(thirdUrl, "GET", resp);
});
.then(function(resp) {
// do whatever keep the chain going or whatever
})
.catch(function(error) {
// if any of the promises error it will immediately call here.
});
With promises you can return a new promise from within a .then and just keep the chain going infinitely.
I'm just biased for async as i think it really improves readability when used right.
you could do something like:
let requestParams = [
[url, 'POST'],
[secondUrl, 'GET'],
...
];
function callRecursive(response){
if(!requestParams.length) return Promise.resolve(response);
let params = requestParams.shift();
if(response) params.push(response);
return restRequest(...params).then(callRecursive);
}
callRecursive().then(successCallbk).catch(errCallBk);
You can supply one or more arguments to bind to your partially applied function.
restRequest(url,"POST").then(restRequest.bind(this,secondUrl, "GET"))
.then(restRequest.bind(this,thirdUrl, "GET"));
Since these are fired off in serial, what you really have is a simple chain of functions (some return promises, some might not) that can compose (or sequence, here) together, which I find to be a neat way to isolate out everything you want to happen and then combine behaviors as needed. It's still a Promise chain under the hood, but expressed as a series. First, a few utility methods to help:
var curry = (f, ...args) =>
(f.length <= args.length) ? f(...args) : (...more) => curry(f, ...args, ...more);
var pipeP = (...fnlist) =>
acc => fnlist.reduce( (acc,fn) => acc.then(fn), Promise.resolve(acc));
then
//make restRequest only return a Promise once it's given its 3rd argument
var restRequest = autocurry(restRequest);
//define what our requests look like
var request1 = restRequest('firstUrl', "POST");//-> curried function, not yet called
var request2 = restRequest('secondUrl', 'GET');//-> curried function, not yet called
//define some simple methods to process responses
var extractURL = x => x.url;//-> simple function
var extractData = x=> x.data;//-> simple function
//final behaviors, i.e. do something with data or handle errors
//var handleData = ... //-> do something with "data"
//var handleError = ... //-> handle errors
//now, create a sort of lazy program chain waiting for a starting value
//that value is passed to request1 as its 3rd arg, starting things off
var handleARequest = pipeP(request1, extractURL, request2, extractData);
//and execute it as needed by passing it a starting request
handleARequest({postdata:5}).then(handleData).catch(handleErrors);
Recursion is the most obvious approach but it's not necessary. An alternative is to build a .then() chain by reducing an array of known parameters (urls and methods).
The process is presented here under "The Collection Kerfuffle".
function asyncSequence(params) {
return params.reduce(function(promise, paramObj) {
return promise.then(function(response) {
return restRequest(paramObj.url, paramObj.method, response);
});
}, Promise.resolve(null)); // a promise resolved with the value to appear as `response` in the first iteration of the reduction.
}
This will cater for any number of requests, as determined by the length of the params array.
Call as follows :
var params = [
{url:'path/1', method:'POST'},
{url:'path/2', method:'GET'},
{url:'path/3', method:'POST'}
];
asyncSequence(params).then(function(lastResponse) {
//all successfully completed
}).catch(function(e) {
// something went wrong
});
I just noticed if I have a method in my class I cannot access it with this keyword in my other prototypes if I call them in async.auto or... please see the sample code for more clarification and my workaround.
Would you do the same? In other words is this the most elegant way in Node.JS?
function foo(config) {
var self = Object.create(foo.prototype)
self.db = nano.db.use(config.db.dbName);
return self
}
foo.prototype.method1 = function () {
// The workaround to use this.db is to store this.db in a variable, is this elegant?!? Would you do the same?
var db = this.db;
async.auto({
check_DB: function (next) {
// do some operations here
next();
},
insert_DB: ['check_DB', function (callback, results) {
// Note1: interestingly this.db is not going to work! in other words here this.db is undefined
db.insert(value, function (err, body) {
//Do some other operations here
})
}]
});
}
foo.prototype.method2 = function () {
// The workaround to use this.db is to store this.db in a variable?!? Would you do the same?
var db = this.db;
db.get("baz", function (err, body) {
// Do some operatiuons
// Note2: interestingly this.db is not going to work here either!
db.get("bar", function (err, response) {
// do some other operations
})
}
});
}
It's exactly right. The async.auto(... function is going to change the scope of "this". It's not particular to async, but to calling a javascript function within another one.
Same deal with db.get(... in method2. It also changes the scope of "this" in the exact same way.
So it is quite normal in javascript code before calling a function where you want to access "this" from the outer scope to assign "this" to some other variable just, just as you've done with:
var db = this.db;
A lot of folks will assign this to something like: "self', "_this", "that", etc.
I am new to Javascript programming coming from a Java and Objective C background. I am looking to learn a bit more about Javascript for hybrid mobile applications.
In doing so I am trying to do a login with a call back but I am having a little trouble understanding both the syntax and the way a callback works.
First up I am calling the following login function which just creates an ajax call to fetch some JSON at the minute for testing purposes:
testLogin.loginWithUsername ("test", loginCallback);
This works OK as I can see the 200 OK Status and the expected JSON in logging.
However the call back "loginCallBack" never gets called.
It is as follows:
loginCallback: {
success: function(id) {
alert ('success');
}
failure: function (id, error) {
alert ('failure');
}
}
First off the above gives me a syntax error when I try to run the code, at the success:function(id) line. I can change it and the failure function to = function(id) and it the code runs then but the callback is never called.
I am using a library for the login that states the call back required is an object that needs a success and failure function and the code above is the given example.
So first off I don't understand why the above syntax works in the sample code but gives me an error when I run it?
Secondly am I calling the callback correctly? Both the loginWithUsername call and the loginCallback function are in the same Login.js file.
Here is an example how callback works:
First thing: you need to create a new object containing your functions/methods. Properties and methods are listed comma seperated.
// Creating new object with callback functions
var loginCallback = {
success: function(id) {
alert ('success');
} , // Add a comma
failure: function (id, error) {
alert ('failure');
}
}
function loginWithUsername(username, callback) {
if (username === 'test') {
var successId = 1;
callback.success(successId);
} else {
var errorId, errorMsg;
errorId = 0;
errorMsg = 'Error';
callback.failure(errorId, errorMsg);
}
}
Now you can call the function:
loginWithUsername('test', loginCallback);
And the result should be 'success'.
Edit:
But you can do this without an object, by passing the function directly:
// Creating function
function showMessage(message) {
alert(message);
}
function loginWithUsername(username, callback) {
if (username === 'test') {
callback('success');
} else {
callback('failure');
}
}
// Pass function
loginWithUsername('test', showMessage); // Result: 'success'
loginWithUsername('abcd', showMessage); // Result: 'failure'
First off the above gives me a syntax error when I try to run the code, at the success:function(id) line.
Each property: value pair in an object literal must be separated with a comma.
}, /* Your comma is missing which is why you get a syntax error */
failure:
This:
loginCallback: {
is only acceptable if you want to define a property inside an object literal.
This:
testLogin.loginWithUsername ("test", loginCallback);
is using a variable.
You probably want:
var loginCallback = {
but it is hard to tell without more context.