React Tabulator with custom ajaxRequestFunc - tabulator

I want to use another framewrok to load the data for the grid. I created the function based on the documentation, but unfortunately it doesn't work. The queryRealm function is not called?
Overriding the Request Promise
async function queryRealm(url: string, config: any, params:any): Promise<any> {
//url - the url of the request
//config - the ajaxConfig object
//params - the ajaxParams object
console.log('test');
//return promise
return await new Promise(async function (resolve, reject) {
//do some async data retrieval then pass the array of row data back into Tabulator
//resolve(data);
resolve([]);
//if there is an error call this function and pass the error message or object into it
reject();
});
};
const options: ReactTabulatorOptions = {
ajaxConfig: "GET",
ajaxURL: "https://localhost:7231/api/....",
dataSendParams: {
page: 'page',
size: 'per_page'
},
ajaxRequestFunc: queryRealm,
};
return (
<ReactTabulator columns={[]} data={[]} options={options} layout={"fitData"} />
);

Related

How can I am make sure these chain of functions in Node.js are performed in order (using promises)?

I have a set of functions in Node.js that I would like to load in a certain order. I will provide some mockup code abstracted and simplified:
function updateMyApp() {
loadDataToServer()
.then(() => useData())
.then(() => saveData())
.then(() => { console.log("updateMyApp done") })
}
function loadDataToServer() {
return new Promise( (resolve, reject) {
...preparing data and save file to cloud...
resolve()})
}
function handleDataItem(item) {
// Function that fetches data item from database and updates each data item
console.log("Name", item.name)
}
function saveData() {
// Saves the altered data to some place
}
useData is a bit more complex. In it I would like to, in order:
console.log('Starting alterData()')
Load data, as json, from the cloud data source
Iterate through every item in the json file and do handleDataItem(item) on it.
When #2 is done -> console.log('alterData() done')
Return a resolved promise back to updateMyApp
Go on with saveData() with all data altered.
I want the logs to show:
Starting useData()
Name: Adam
Name: Ben
Name: Casey
useData() done
my take on this is the following:
function useData() {
console.log('Starting useData()')
return new Promise( function(resolve, reject) {
readFromCloudFileserver()
.then(jsonListFromCloud) => {
jsonListFromCloud.forEach((item) => {
handleDataItem(item)
}
})
.then(() => {
resolve() // I put resolve here because it is not until everything is finished above that this function is finished
console.log('useData() done')
}).catch((error) => { console.error(error.message) })
})
}
which seems to work but, as far as I understand this is not how one is supposed to do it. Also, this seems to do the handleDataItem outside of this chain so the logs look like this:
Starting useData()
useData() done
Name: Adam
Name: Ben
Name: Casey
In other words. It doesn't seem like the handleDataItem() calls are finished when the chain has moved on to the next step (.then()). In other words, I can not be sure all items have been updated when it goes on to the saveData() function?
If this is not a good way to handle it, then how should these functions be written? How do I chain the functions properly to make sure everything is done in the right order (as well as making the log events appear in order)?
Edit: As per request, this is handleDataItem less abstracted.
function handleDataItem(data) {
return new Promise( async function (resolve) {
data['member'] = true
if (data['twitter']) {
const cleanedUsername = twitterApi.cleanUsername(data['twitter']).toLowerCase()
if (!data['twitter_numeric']) {
var twitterId = await twitterApi.getTwitterIdFromUsername(cleanedUsername)
if (twitterId) {
data['twitter_numeric'] = twitterId
}
}
if (data['twitter_numeric']) {
if (data['twitter_protected'] != undefined) {
var twitterInfo = await twitterApi.getTwitterGeneralInfoToDb(data['twitter_numeric'])
data['twitter_description'] = twitterInfo.description
data['twitter_protected'] = twitterInfo.protected
data['twitter_profile_pic'] = twitterInfo.profile_image_url.replace("_normal", '_bigger')
data['twitter_status'] = 2
console.log("Tweeter: ", data)
}
} else {
data['twitter_status'] = 1
}
}
resolve(data)
}).then( (data) => {
db.collection('people').doc(data.marker).set(data)
db.collection('people').doc(data.marker).collection('positions').doc(data['report_at']).set(
{
"lat":data['lat'],
"lon":data['lon'],
}
)
}).catch( (error) => { console.log(error) })
}
The twitterAPI functions called:
cleanUsername: function (givenUsername) {
return givenUsername.split('/').pop().replace('#', '').replace('#', '').split(" ").join("").split("?")[0].trim().toLowerCase()
},
getTwitterGeneralInfoToDb: async function (twitter_id) {
var endpointURL = "https://api.twitter.com/2/users/" + twitter_id
var params = {
"user.fields": "name,description,profile_image_url,protected"
}
// this is the HTTP header that adds bearer token authentication
return new Promise( (resolve,reject) => {
needle('get', endpointURL, params, {
headers: {
"User-Agent": "v2UserLookupJS",
"authorization": `Bearer ${TWITTER_TOKEN}`
}
}).then( (res) => {
console.log("result.body", res.body);
if (res.body['errors']) {
if (res.body['errors'][0]['title'] == undefined) {
reject("Twitter API returns undefined error for :'", cleanUsername, "'")
} else {
reject("Twitter API returns error:", res.body['errors'][0]['title'], res.body['errors'][0]['detail'])
}
} else {
resolve(res.body.data)
}
}).catch( (error) => { console.error(error.message) })
})
},
// Get unique id from Twitter user
// Twitter API
getTwitterIdFromUsername: async function (cleanUsername) {
const endpointURL = "https://api.twitter.com/2/users/by?usernames="
const params = {
usernames: cleanUsername, // Edit usernames to look up
}
// this is the HTTP header that adds bearer token authentication
const res = await needle('get', endpointURL, params, {
headers: {
"User-Agent": "v2UserLookupJS",
"authorization": `Bearer ${TWITTER_TOKEN}`
}
})
if (res.body['errors']) {
if (res.body['errors'][0]) {
if (res.body['errors'][0]['title'] == undefined) {
console.error("Twitter API returns undefined error for :'", cleanUsername, "'")
} else {
console.error("Twitter API returns error:", res.body['errors'][0]['title'], res.body['errors'][0]['detail'])
}
} else {
console.error("Twitter API special error:", res.body)
}
} else {
if (res.body['data']) {
return res.body['data'][0].id
} else {
//console.log("??? Could not return ID, despite no error. See: ", res.body)
}
}
},
You have 3 options to deal with your main issue of async methods in a loop.
Instead of forEach, use map and return promises. Then use Promise.all on the returned promises to wait for them to all complete.
Use a for/of loop in combination with async/await.
Use a for await loop.
It sounds like there's a problem in the implementation of handleDataItem() and the promise that it returns. To help you with that, we need to see the code for that function.
You also need to clean up useData() so that it properly returns a promise that propagates both completion and errors.
And, if handleDataItem() returns a promise that is accurate, then you need to change how you do that in a loop here also.
Change from this:
function useData() {
console.log('Starting useData()')
return new Promise( function(resolve, reject) {
readFromCloudFileserver()
.then(jsonListFromCloud) => {
jsonListFromCloud.forEach((item) => {
handleDataItem(item)
}
})
.then(() => {
resolve() // I put resolve here because it is not until everything is finished above that this function is finished
console.log('useData() done')
}).catch((error) => { console.error(error.message) })
})
}
to this:
async function useData() {
try {
console.log('Starting useData()')
const jsonListFromCloud = await readFromCloudFileserver();
for (let item of jsonListFromCloud) {
await handleDataItem(item);
}
console.log('useData() done');
} catch (error) {
// log error and rethrow so caller gets the error
console.error(error.message)
throw error;
}
}
The structural changes here are:
Switch to use async/await to more easily handle the asynchronous items in a loop
Remove the promise anti-pattern that wraps new Promise() around an existing promise - no need for that AND you weren't capturing or propagating rejections from readFromCloudFileServer() which is a common mistake when using that anti-pattern.
rethrow the error inside your catch after logging the error so the error gets propagated back to the caller

Node Js loop in Promise All not waiting to get request from SOAP

Hi I try to use for for loop data and send request to soap api. The problem is my program seems like it didn't wait to get request from soap request
here is my example data
data = [
{
store: "store1",
product: "product2",
},
{
store: "store2",
product: "product3",
}
]
exports.thisIsFunc = async (req, res, next) => {
Promise.all(data).then(result => {
for (let item of result) {
i++
console.log(i)
args.CustomerCode = item.store
args.ItemNo = item.product
const getApi = apiSend()
}
});
}
export function apiSend() {
return new Promise ((resolve, reject) => {
soap.createClient(url, (err, client) => {
client.getDataFromAPI(args, (err, result) => {
console.log(result)
return result
})
});
});
}
as you see I try to use new Promise in sendApi function but sometimes It stop the error show up
TypeError: Cannot read property 'getDataFromAPI' of undefined
sometimes it return response from api. The reason that I didn't use async,await in soap because I try to change soap function into async function but it didn't work.
apiSend() has the following issues:
You ignore both callback errors.
You never resolve() or reject() the promise you return.
You can fix it like this:
export function apiSend() {
return new Promise ((resolve, reject) => {
soap.createClient(url, (err, client) => {
if (err) return reject(err);
client.getDataFromAPI(args, (err, result) => {
if (err) return reject(err);
console.log(result)
resolve(result);
})
});
});
}
Then, in thisIsFunc() you have a number of issues:
There's no point in using Promise.all() on a static array of values. It only does something useful if you pass it an array with at least one promise in it.
There's no declaration of i or args
You don't do anything with the promise returns from apiSend()
It's not really clear what you're trying to do here, but if you want thisIsFunc() to return a promise that resolves with an array of results from all the calls to apiSend(), that could be structured like this:
exports.thisIsFunc = () => {
return Promise.all(data.map(item => {
let args = {
CustomerCode: item.store,
ItemNo: item.product
};
return apiSend(args);
}));
}
This implementation uses data.map() to iterate the array and create an array of promise from calling apiSend(). It then uses Promise.all() to collect all the results from the array of promises into an array of results that is the resolved value of the single returned promise.
It appears you were attempting to declare thisIsFunc() as an Express request handler. You can do that, but then you will need to complete the request inside that function by sending a response for both success and error conditions. As I've shown it above, this is a helper function that retrieves a set of data and then returns a promise that resolves to an array of results. You can use that in a request handler as needed or you can add more code to this to make it into a request handler that sends a response to the incoming request and returns errors responses appropriately.

Async - Await issue using Twitter API

I'm currently trying to practice using an API with Twitter API.
I'm using Twit package to connect to twitters API but when I try to do a get request I get
Promise { pending }
I have tried using Async-Await but I'm not sure what I'm doing wrong here.
Here is my code:
const Twit = require('twit');
const twitterAPI = require('../secrets');
//Twit uses OAuth to stablish connection with twitter
let T = new Twit({
consumer_key: twitterAPI.apiKey,
consumer_secret: twitterAPI.apiSecretKey,
access_token: twitterAPI.accessToken,
access_token_secret: twitterAPI.accessTokenSecret
})
const getUsersTweets = async (userName) => {
let params = { screen_name: userName, count: 1 }
const userTweets = await T.get('search/tweets', params, await function (err, data, response) {
if (err) {
return 'There was an Error', err.stack
}
return data
})
return userTweets
}
console.log(getUsersTweets('Rainbow6Game'));
Problem
The biggest assumption that is wrong with the sample code is that T.get is expected to eventually resolve with some data.
const userTweets = await T.get('search/tweets', params, await function (err, data, response) {
if (err) {
return 'There was an Error', err.stack
}
return data // 'data' returned from here is not necessarily going to be received in 'userTweets' variable
})
callback function provided as last argument in T.get function call doesn't have to be preceded by an 'await'.
'data' returned from callback function is not necessarily going to be received in 'userTweets' variable. It totally depends on how T.get is implemented and can not be controlled.
Reason
Thing to be noted here is that async / await works well with Promise returning functions which eventually get resolved with expected data, however, that is not guaranteed here
Relying on the result of asynchronous T.get function call will probably not work because it returns a Promise { pending } object immediately and will get resolved with no data. The best case scenario is that everything with your function will work but getUsersTweets function will return 'undefined'.
Solution
The best solution is to make sure that your getUsersTweets function returns a promise which eventually gets resolved with correct data. Following changes are suggested:
const getUsersTweets = (userName) => {
return new Promise ((resolve, reject) => {
let params = { screen_name: userName, count: 1 }
T.get('search/tweets', params, function (err, data, response) {
if (err) {
reject(err);
}
resolve(data);
})
}
}
The above function is now guaranteed to return expected data and can be used in the following way:
const printTweets = async () => {
const tweets = await getUsersTweet(userName);
console.log(tweets);
}
printTweets();
From what I can see on your code, getUserTweets is an async function, so it will eventually return a promise. I'm assuming you will use this value on another function, so you will need to use it inside an async function and use await, otherwise you will always get a promise.
const logTweets = async (username) => {
try {
const userTweets = await getUsersTweets(username);
// Do something with the tweets
console.log(userTweets);
catch (err) {
// catch any error
console.log(err);
}
}
If logging is all you want and you wrapped it inside a function in which you console.log it, you can call that function directly:
logTweets('someUsername');

Call external API until request is marked as complete

I have following situation.
I have a service which runs jobs on a remote service and exposes API for calling the same.
I have an array of jobs which needs to be executed on remote server and it works fine, when used Promises.
The flow is as below
Inside main function I get a token. On .then() of the same, I initiate my for loop for jobs and pass the JobID and token. This second function returns me the execution_ID of each job. On the .then() of this second function I pass token and execution_id. This returns me the status of the job.
My problem is; when a job is executed, it send me queued, initiated and completed as status. When the status turns to 'completed' I get results of the job; which I need to display.
I tried using a setTimeOut() inside the last function, but I'm not sure when it will end as each job may take different time.
Is there a way I can call this third function multiple time unless the status changes to 'completed'?
Below is the code
app.get('/ExecuteJobs', function (req, res) {
var commandArraay = ["job1", "job2"]
var sTokenID;
getAuthToken()
.then(function (token) {
commandArraay.forEach(function (element) {
getExecutionID(token, element)
.then(function (executionResult) {
setTimeout(() => {
getExecutionResult(token, executionResult.id, executionResult)
.then(function (updateArray) {
console.log("Final Status " + "ID: " + executionResult.id + " Status: " + executionResult.status);
// if(executionResult.)
})
}, 10000)
})
})
})
res.send('Done');
});
// Function to get the auth token
async function getAuthToken(token) {
return new Promise(function (resolve, reject) {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0;
var pUserID = 'uname'
var pPwd = 'pwd'
performhttpsRequest('/<remote_api>/tokens', 'POST', {
sAuth: "Basic " + new Buffer(pUserID + ":" + pPwd).toString("base64")
}, "0", token,
function (data1) {
sTokenID = data1.token;
resolve(sTokenID);
})
})
}
// Function to post the command and get execution ID
async function getExecutionID(tokenID, command, executionID) {
return new Promise(function (resolve, reject) {
performhttpsRequest('/<remote_api>/executecommand', 'POST', {
command: command
}, "1", tokenID,
function (data1) {
var executionID = data1.execution_id;
resolve(executionID);
})
})
}
// Function to get the execution results for an ID
async function getExecutionResult(tokenID, executionID, result) {
return new Promise(function (resolve, reject) {
performhttpsRequest('/<remote_api>/execution_result/' + executionID, 'GET', {
}, "1", tokenID,
function (result) {
resolve(result.result);
})
})
}
If you absolutely need a result after the enqueued job is settled, then I don't see how you can have any other choice other than retrying to check the status every n-seconds.
Here's how I would do it. A recursive function that retries a request n-times, each time waiting n-seconds:
// Mock request, resolves { status: 'completed' } 20% of the time.
const request = () => {
return new Promise(resolve => {
Math.random() < 0.2
? resolve({ status: 'completed', foo: 'bar' })
: resolve({ status: 'pending' })
})
}
const retryToCompletion = async ({ wait, retries }) => {
console.log('Retries left:', retries)
const result = await new Promise((resolve, reject) => {
setTimeout(() => request().then(resolve).catch(reject), wait)
})
if (result.status === 'completed')
return result
if (retries)
return retryToCompletion({ wait, retries: --retries })
throw new Error('Retry attempts exhausted')
}
retryToCompletion({ wait: 1000, retries: 5 })
.then(console.log)
.catch(console.error)
That being said, some API's that work with BASE queues offer a handle to a WebSocket connection that notifies when a job is completed. If the API offers that, then you should ditch retrying and use the completed notification instead.
making GET calls until completion
/**
* cbk be called when..complete execution ok
*/
function completeExecution(tokenID, executionID, result, callPeriod, cbk){
return getExecutionResult(tokenID, executionID, result).then(res=>{
if(res.completed){
return cbk(null, res)
}
setTimeout(function(){
return completeExecution(cbk); //reenqueue
}, callPeriod)
}).catch(cbk) //up to you to abort or insist
}
Then you can promisify completeExecution (with util.promisify or by yourself)

Chaining together promises with a db insert

I'm struggling trying to chain together three requests that require synchrony in node.js. Here is my attempt at using promises, but i am getting an error saying that db.run isn't a function. The first action should insert into my sqlite db. The most important thing i need is the
this.lastID variable, which lists the id of the last enetered item. Before attempting to use promises, I was having trouble with scoping. This is important because i need to take this value and use use it in my JSON object under the callback key. Lastly, Im using the requests npm package to send the request.
I am using the bluebird promises library, sqlite3 npm package, nodejs, express.
Any help with this would be awesome because I'm lost.
function db() {
return new Promise(function(resolve, reject) {
db.run(`INSERT INTO scan_requests(name, date) VALUES(?,?);`, [name,date], function(err) {
if (err) {
console.log(err)
}
let q = this.lastID
resolve(q)
})
})
}
db()
.then(function(q) {
let options = {
url: 'API',
body: {
name: req.name,
scan_callback: `http://localhost:80/${q}`
},
json: true
}
resolve(options)
}).then(function(options) {
console.log(options)
})
1st rule of "promises" ... always return your "promises". Except when you create a new one.
Try this ...
app.post('/route', function (req,res) {
new Promise(function(resolve, reject) {
db.run(`INSERT INTO scan_requests(req.name,req.date) VALUES(?,?);`, [name,date]).then(function(result) {
let options = {
url: 'http://API',
body: {
name: req.name,
date: req.date
callback: `http://localhost:80/${this.lastID}`,
},
json: true
}
// this resolves this promise ... it is now passed on
resolve(options);
}).then(function(options) {
// options is now the result from the promise
console.log(options)
request
.post(options)
.on('error', function(err) {
console.log(err)
})
.pipe(res)
});
});
});
UPDATE (question modified)
You're using resolve(options) but resolve is not in scope there (it doesn't exist). Remember the first rule of promises ...
db()
.then(function(q) {
let options = {
url: 'API',
body: {
name: req.name,
scan_callback: `http://localhost:80/${q}`
},
json: true
}
// *** change the following line ***
// --- you must return your data ---
return options;
}).then(function(options) {
console.log(options)
// -------------------------
// --- contrived example ---
// -------------------------
return { success: true };
}).then(status => {
console.log(`Success ${status.success}`);
});
The example includes a rather useless but illustrative example of how to continue passing data down the "promise chain".

Resources