I'm programming a method that uses web3js to transfer a token using a smart contract.
When you launch the transfer event, you get as a result the txHash, and if you want to get all the other values associated to the transfer, you have to subscribe to an event and wait for it to happen to get the values.
I have to return the values to the customer, so I subscribe to the transfer event and wait for it to broadcast to return the data.
The problem is that this might take a long time (think from 10 seconds to hours) and it gives me a timeout sometimes, so the frontend team suggested to inform me a webhook endpoint and I forward the event information to it when it happens.
So I have to split the process into two:
Do the transfer and inform the txHash, and start a separate process (2) that listens for the event.
Subscribe to the event and, when it happens, forward it to the webhook provided.
My code right now looks something like this:
function transfer(req, res, next) {
try {
contractRouter.transfer(from, to, tokenId).then(function(result){
transferToWebhook(whHostname, whPath, result);
next();
}).fail(function(err){
return res.status(500).json({status: 'error', name: err.name, message: err.message});
}
catch (ex) {
return res.status(500).json({status: 'error', name: ex.name, message: ex.message});
}
}
and the function that transfers to webhook looks like this:
function transferToWebhook(whHostname, whPath, txHash){
contractRouter.getTransferEvent(txHash).then(function(result){
var postData = JSON.stringify(result);
var options = {
hostname: whHostname,
port: 80,
path: whPath,
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
}
var req = https.request(options, (res) => {
console.log(`STATUS: ${res.statusCode}`);
console.log(`HEADERS: ${JSON.stringify(res.headers)}`);
res.setEncoding('utf8');
res.on('data', (chunk) => {
console.log(`BODY: ${chunk}`);
});
res.on('end', () => {
console.log('No more data in response.');
});
});
req.on('error', (e) => {
console.log(`problem with request: ${e.message}`);
});
req.write(postData);
req.end();
});
}
The function that subscribes to the transfer event looks like this:
function getTransferEvent(txHash){
var deferred = q.defer();
ethereumHandler.setContract(config.get(cABIAddress), cAddress).then(function(abiContract){
Promise.resolve(txHash).then(function resolver(txHash){
abiContract.getPastEvents('Transfer',{filter: {transactionHash: txHash}}, function(error, events){})
.then(function(event){
var returnValues = {
from: event.returnValues.from,
to: event.returnValues.to,
tokenId: event.returnValues.tokenId
}
deferred.resolve(returnValues);
});
});
return deferred.promise;
});
}
The code for the last function works if I put it straight on the transfer function, but it's not called if I try to call it through the transferToWebhook function.
How can I launch the transferToWebhook function after having answered the first request?
You can spawn your process, using the spawn() method from the child_process module, then listen on the event (process.on('data')) and use a promsise to consume the returned datas. I am not sure it's gonna solve your case as your function is an object of contractRouter contractRouter.getTransferEvent(txHash), but you should be able to adapt it in some way. See an example of what i mean.
in your file.js
const { spawn } = require('child_process')
function transfertToWebHook(data) {
getTransfertEvent(data)
.then((result) => {
const dts = JSON.parse(result)
console.log('the res: ', dts)
// send the res back
})
.catch(e => console.log('handle the err: ', e))
console.log('carry on mother process')
}
function getTransfertEvent(data) {
return new Promise((resolve, reject) => {
const sp = spawn(process.execPath, ['childProcess.js'])
// pass the arguments, here it will be the txHash
sp.stdin.write(data)
sp.stdin.end()
sp.stdout.on('data', (d) => {
// when datas get proceed you get it back.
resolve(d.toString())
})
sp.stdout.on('error', (e) => {
reject(e)
})
console.log('run what ever need to be proceed on the mother process')
})
}
transfertToWebHook('test')
create an other file name childProcess.js.
Use the Tranform stream to process the process.sdtin datas then return them through the process.stdout
const { Transform, pipeline } = require('stream')
const createT = () => {
return new Transform({
transform(chunk, enc, next) {
// here run the code of you getTransfertEventFunction()
// simulate some async process
setTimeout(() => {
// chunk is your passed arguments
// !! chunk is a buffer so encode it as utf8 using 'toString()'
// make it upperCase to simulate some changement
const dt = chunk.toString().toUpperCase()
// return an object as it's what your func return
const proceededDatas = {
name: dt,
from: "from datas",
to: "to datas",
tokenId: "the token"
}
const dataString = JSON.stringify(proceededDatas)
next(null, dataString)
}, 1000)
}
})
}
pipeline(process.stdin, createT(), process.stdout, e => console.log(e))
run the code: node file.js
Related
i am working with some Third Party api that for some reason ask to send body with GET request, weird but i don't have control over that. Node-Fetch is not allowing sending body with GET request so i am using https lib. My issue is that i am trying to save the response as a variable but get undefined over and over again. If i console.log the results i can see the response and its all great but i need the data as a variable.
var req = https.request(url, options, (res) => {
res.on("data", (chunk) => {
finalResponse += chunk
});
res.on('end', () => {
console.log(finalResponse); //Working
})
});
req.on("error", (e) => {
console.error(e);
});
req.write(postData);
req.end();
console.log('RESULTS: ' + finalResponse); //Undefined
It's undefined because Node.js runs asynchronously, so the result is logged before it's actually finished.
In order to get the data, you'll need to wait for the request to finish from your consumer, which is accomplished by using promises, i.e. using async/await, and promisifying the request method, which means making it return a promise to the consumer, and resolve it when the request is finished.
Also, response is usually a Buffer, and if you need string, you can convert it.
Try this:
function getFinalResponse() {
// return a Promise and resolve
// when the request is done
return new Promise((resolve, reject) => {
var req = https.request(url, options, (res) => {
const finalResponse = [];
res.on("data", (chunk) => {
finalResponse.push(chunk);
});
res.on('end', () => {
console.log(finalResponse); //Working
// if it's a string then you can use
// Buffer.concat(finalResponse).toString();
resolve(Buffer.concat(finalResponse));
})
});
req.on("error", (e) => {
console.error(e);
reject(e);
});
req.write(postData);
req.end();
});
}
// consumer
async function run() {
console.log('RESULTS: ', await getFinalResponse); //Undefined
}
// call the consumer
run();
I'm using the http2 client package with nodeJS. I want to execute a simple get request, and await the response from the server. So far I have
import * as http2 from "http2";
...
const clientSession = http2.connect(rootDomain);
...
const req = clientSession.request({ ':method': 'GET', ':path': path });
let data = '';
req.on('response', (responseHeaders) => {
// do something with the headers
});
req.on('data', (chunk) => {
data += chunk;
console.log("chunk:" + chunk);
});
req.on('end', () => {
console.log("data:" + data);
console.log("end!");
clientSession.destroy();
});
process.exit(0);
But what I"m not able to figure out is how to do I await the response of the request before exiting? Right now the code flies through to the process.exit line and I can't see a way to block until the request is done.
If you want to await it, then you have to encapsulate it into a function that returns a promise and you can then use await on that promise. Here's one way to do that:
import * as http2 from "http2";
...
function getData(path) {
return new Promise((resolve, reject) => {
const clientSession = http2.connect(rootDomain);
const req = clientSession.request({ ':method': 'GET', ':path': path });
let data = '';
req.on('response', (responseHeaders) => {
// do something with the headers
});
req.on('data', (chunk) => {
data += chunk;
console.log("chunk:" + chunk);
});
req.on('end', () => {
console.log("data:" + data);
console.log("end!");
clientSession.destroy();
resolve(data);
});
req.on('error', (err) => {
clientSession.destroy();
reject(err);
});
});
}
async function run() {
let data = await getData(path);
// do something with data here
}
run().then(() => {
process.exit(0);
}).catch(err => {
console.log(err);
process.exit(1);
});
The other way to do this is to use a higher level http library that does much of this work for you. Here's an example using the got module:
import got from 'got';
async function run() {
let data = await got(url, {http2: true});
// do something with data here
}
In this case, the got() module already supports http2 for you (if you specify that option), already collects the whole response for you and already supports promises (all the things your code needed to add in your original version).
Note, the GET method is the default method which is why it is not necessary to specify it here.
response = await new Promise(async (resolve, reject)=> {
let data = '';
req.on('data', async (chunk) => {
data += chunk;
resolve(JSON.parse(data));
});
});
Yes, I have seen many other questions and answers. I know I need to use a callback response. However, I still don't get how to do this particular example. Most examples involve a callback response that logs something or the post has hundreds of different answers.
How do I return the request response from getPageData?
var url = "myurl";
var name = await getPageData(url);
// wait until I get name and then do stuff with name
function getPageData(url)
{
const https = require('https');
https.get(url, (resp) => {
let data = '';
resp.on('data', (chunk) => {
data += chunk;
});
resp.on('end', () => {
var name = JSON.parse(data);
// what do I do here to get name out?
});
}).on("error", (err) => {
console.log("Error: " + err.message);
});
}
await can only be used in async functions. You can however return a promise from getPageData and "await" using chained then:
Use the Promise object:
const https = require('https');
var url = "myurl";
var name;
getPageData(url)
.then(data => { name = data; /*This is the scope in which you would use name*/ })
.catch(err => { console.log('Error occured', err); });
async function getPageData(url) {
return new Promise((resolve, reject) => {
https
.get(url, resp => {
let data = '';
resp.on('data', chunk => {
data += chunk;
});
resp.on('end', () => {
const name = JSON.parse(data);
// what do I do here to get name out?
resolve(name);
});
})
.on('error', err => {
console.log(`Error: ${err.message}`);
reject(err);
});
});
}
The higher level solution here is to use a module for making http requests that already supported promises. You can see a list of many of them here. My favorite from that list is got() and you can use it to solve your problem like this:
function getPageData(url) {
return got(url);
}
// can only use await inside a function declared as async
async function someFunction() {
try {
let name = await getPageData(url);
console.log(name);
} catch(e) {
console.log(e);
}
}
The got() library does a whole bunch of things for you.
It is entirely based on promises so you can directly use await on the promise it returns.
It collects the whole response for you (you don't have to write your own code to do that).
If the response is JSON, it automatically parses that for you and resolves to the parsed Javascript object.
It automatically detects http or https URL and uses the right low level module.
And, it has dozens of other useful features (not needed in this example).
Or, if you want the lower level solution where you make your own promisified function for doing an https request, you can do this:
const https = require('https');
// can only use await inside a function declared as async
async function someFunction() {
const url = "myurl";
try {
let name = await getPageData(url);
console.log(name);
} catch(e) {
console.log(e);
}
}
function getPageData(url) {
return new Promise((resolve, reject) => {
https.get(url, (resp) => {
let data = '';
resp.on('data', (chunk) => {
data += chunk;
});
resp.on('end', () => {
try {
const name = JSON.parse(data);
resolve(name);
} catch(e) {
// JSON parsing error
reject(e);
}
});
}).on("error", (err) => {
console.log("Error: " + err.message);
reject(err);
});
}).on('error', (err) => {
console.log("Error: " + err.message);
reject(err);
});
}
I am calling an API gateway URL from a Lambda using HTTPS request inside promise, the code looks like:
async function invoke(args) {
console.log("Calling API") <----- Logging on cloud watch
const { options, postData } = args
let errorObj = {
status: '500',
ErrorCode: 'null'
}
return new Promise((resolve, reject) => {
console.log("Inside Promise") <----- Logging on cloud watch
const req = http.request(options, (res) => {
console.log("Inside https request>>", res) <-- NOT LOGGING
res.setEncoding('utf8');
let body = []
res.on('data', (chunk) => {
console.log("Inside On data") <-- NOT LOGGING
body.push(chunk)
})
res.on('end', () => {
console.log("Inside end") <-- NOT LOGGING
try {
if (res.statusCode < 200 || res.statusCode >= 300) {
reject(errorObj)
}
resolve(body)
} catch(e) {
reject(errorObj)
}
})
})
req.on("error", err => {
console.log('req error >>', err)
reject(err)
})
console.log('req>>', req) <----- Logging on cloud watch
if (postData) {
req.write(JSON.stringify(postData))
}
req.end()
})
}
I am calling "invoke' method from handler. Am I missing anything. Whenever I send a smaller payload I can see all the console logs, but payload size of 2MB skips the request method, but in API gateway I can still see the request going. Why invoke method not waiting for the response to be resolved. TIA.
I would double check that you are using the await keyword when calling invoke() from the handler
e.g. e.g. const result = await invoke()
I'm new to electron and trying to build an application to control smarthome-components from my Mac. To do this, I need many HTTP-Request so the idea is to make an own method/function for this job.
Now my problem is, that I don't know how to use this callback-thing ;)
This is my code now:
const {app, Tray, Menu, BrowserWindow, net} = require('electron');
const path = require('path');
const iconPath = path.join(__dirname, 'icon.png');
let appIcon = null;
let win = null;
var http = require('http');
function httpGet(url, callback) {
http.get(url, (res) => {
const { statusCode } = res;
const contentType = res.headers['content-type'];
res.setEncoding('utf8');
let rawData = '';
res.on('data', (chunk) => { rawData += chunk; });
res.on('end', () => {
return callback(rawData);
});
}).on('error', (e) => {
console.error(`Got error: ${e.message}`);
});
}
app.on('ready', function(){
win = new BrowserWindow({show: false});
appIcon = new Tray(iconPath);
var contextMenu = Menu.buildFromTemplate([
{
label: 'http',
click: function() {
console.log(httpGet('http://192.168.178.10/switches/status_1'),
function(result) {
console.log(result);
}
);
}
}
]);
appIcon.setToolTip('This is my application.');
appIcon.setContextMenu(contextMenu);
});
Trey works, but the httpGet function does not return anything (undefined [function]) and electron crashes.
Would be really thankful if someone could help me with this.
Greetings,
Lukas
Bad news is that I do not know electron, so the TL;DR for the text below is as simple as: put a console.log line into the callback passed to http.get.
There is no return value in httpGet (and it is normal), that is why you can not log it. JavaScript event handling works with callbacks, and getting data via HTTP is an event.
What you have as second argument for http.get is the event handler.
While now it appears as a fancy lambda,
(res)=>{...}
in oldschool way it would look like
function(res){...}
So that is a function, and it will get invoked when something happens to the HTTP request you have issued, and that is where you could log/dig into the result (res).
What you see in your log at the moment:
undefined is the return value of a function which does not return anything, httpGet
[function] is the function you pass as second argument for console.log
function a(){} // a function which does not return anything
console.log(a(),a); // logs function result and function itself
First of all, the callback pattern is usually to write a function that accepts 2 parameters: err and result. If there was no error err becomes null, if there was an error result becomes null. So this would be the best way to write httpGet:
function httpGet(url, callback) {
http.get(url, (res) => {
const {
statusCode
} = res;
const contentType = res.headers['content-type'];
res.setEncoding('utf8');
let rawData = '';
res.on('data', (chunk) => {
rawData += chunk;
});
res.on('end', () => {
return callback(null, rawData);
});
}).on('error', (e) => {
console.error(`Got error: ${e.message}`);
return callback(e, null)
});
}
Second, the way you wrote your call to httpGet is wrong. Change this:
console.log(httpGet('http://192.168.178.10/switches/status_1'),
function(result) {
console.log(result);
}
);
To
httpGet('http://192.168.178.10/switches/status_1', function(err, rawData) {
if (err) {
console.error('httpGet failed!');
} else {
console.log(rawData)
}
});