If I need to call 3 http API in sequential order, what would be a better alternative to the following code:
http.get({ host: 'www.example.com', path: '/api_1.php' }, function(res) {
res.on('data', function(d) {
http.get({ host: 'www.example.com', path: '/api_2.php' }, function(res) {
res.on('data', function(d) {
http.get({ host: 'www.example.com', path: '/api_3.php' }, function(res) {
res.on('data', function(d) {
});
});
}
});
});
}
});
});
}
Using deferreds like Futures.
var sequence = Futures.sequence();
sequence
.then(function(next) {
http.get({}, next);
})
.then(function(next, res) {
res.on("data", next);
})
.then(function(next, d) {
http.get({}, next);
})
.then(function(next, res) {
...
})
If you need to pass scope along then just do something like this
.then(function(next, d) {
http.get({}, function(res) {
next(res, d);
});
})
.then(function(next, res, d) { })
...
})
I like Raynos' solution as well, but I prefer a different flow control library.
https://github.com/caolan/async
Depending on whether you need the results in each subsequent function, I'd either use series, parallel, or waterfall.
Series when they have to be serially executed, but you don't necessarily need the results in each subsequent function call.
Parallel if they can be executed in parallel, you don't need the results from each during each parallel function, and you need a callback when all have completed.
Waterfall if you want to morph the results in each function and pass to the next
endpoints =
[{ host: 'www.example.com', path: '/api_1.php' },
{ host: 'www.example.com', path: '/api_2.php' },
{ host: 'www.example.com', path: '/api_3.php' }];
async.mapSeries(endpoints, http.get, function(results){
// Array of results
});
sync-request
By far the most easiest one I've found and used is sync-request and it supports both node and the browser!
var request = require('sync-request');
var res = request('GET', 'http://google.com');
console.log(res.body.toString('utf-8'));
That's it, no crazy configuration, no complex lib installs, although it does have a lib fallback. Just works. I've tried other examples here and was stumped when there was much extra setup to do or installs didn't work!
Notes:
The example that sync-request uses doesn't play nice when you use res.getBody(), all get body does is accept an encoding and convert the response data. Just do res.body.toString(encoding) instead.
You could do this using my Common Node library:
function get(url) {
return new (require('httpclient').HttpClient)({
method: 'GET',
url: url
}).finish().body.read().decodeToString();
}
var a = get('www.example.com/api_1.php'),
b = get('www.example.com/api_2.php'),
c = get('www.example.com/api_3.php');
I'd use a recursive function with a list of apis
var APIs = [ '/api_1.php', '/api_2.php', '/api_3.php' ];
var host = 'www.example.com';
function callAPIs ( host, APIs ) {
var API = APIs.shift();
http.get({ host: host, path: API }, function(res) {
var body = '';
res.on('data', function (d) {
body += d;
});
res.on('end', function () {
if( APIs.length ) {
callAPIs ( host, APIs );
}
});
});
}
callAPIs( host, APIs );
edit: request version
var request = require('request');
var APIs = [ '/api_1.php', '/api_2.php', '/api_3.php' ];
var host = 'www.example.com';
var APIs = APIs.map(function (api) {
return 'http://' + host + api;
});
function callAPIs ( host, APIs ) {
var API = APIs.shift();
request(API, function(err, res, body) {
if( APIs.length ) {
callAPIs ( host, APIs );
}
});
}
callAPIs( host, APIs );
edit: request/async version
var request = require('request');
var async = require('async');
var APIs = [ '/api_1.php', '/api_2.php', '/api_3.php' ];
var host = 'www.example.com';
var APIs = APIs.map(function (api) {
return 'http://' + host + api;
});
async.eachSeries(function (API, cb) {
request(API, function (err, res, body) {
cb(err);
});
}, function (err) {
//called when all done, or error occurs
});
As of 2018 and using ES6 modules and Promises, we can write a function like that :
import { get } from 'http';
export const fetch = (url) => new Promise((resolve, reject) => {
get(url, (res) => {
let data = '';
res.on('end', () => resolve(data));
res.on('data', (buf) => data += buf.toString());
})
.on('error', e => reject(e));
});
and then in another module
let data;
data = await fetch('http://www.example.com/api_1.php');
// do something with data...
data = await fetch('http://www.example.com/api_2.php');
// do something with data
data = await fetch('http://www.example.com/api_3.php');
// do something with data
The code needs to be executed in an asynchronous context (using async keyword)
Another possibility is to set up a callback that tracks completed tasks:
function onApiResults(requestId, response, results) {
requestsCompleted |= requestId;
switch(requestId) {
case REQUEST_API1:
...
[Call API2]
break;
case REQUEST_API2:
...
[Call API3]
break;
case REQUEST_API3:
...
break;
}
if(requestId == requestsNeeded)
response.end();
}
Then simply assign an ID to each and you can set up your requirements for which tasks must be completed before closing the connection.
const var REQUEST_API1 = 0x01;
const var REQUEST_API2 = 0x02;
const var REQUEST_API3 = 0x03;
const var requestsNeeded = REQUEST_API1 | REQUEST_API2 | REQUEST_API3;
Okay, it's not pretty. It is just another way to make sequential calls. It's unfortunate that NodeJS does not provide the most basic synchronous calls. But I understand what the lure is to asynchronicity.
It seems solutions for this problem is never-ending, here's one more :)
// do it once.
sync(fs, 'readFile')
// now use it anywhere in both sync or async ways.
var data = fs.readFile(__filename, 'utf8')
http://alexeypetrushin.github.com/synchronize
use sequenty.
sudo npm install sequenty
or
https://github.com/AndyShin/sequenty
very simple.
var sequenty = require('sequenty');
function f1(cb) // cb: callback by sequenty
{
console.log("I'm f1");
cb(); // please call this after finshed
}
function f2(cb)
{
console.log("I'm f2");
cb();
}
sequenty.run([f1, f2]);
also you can use a loop like this:
var f = [];
var queries = [ "select .. blah blah", "update blah blah", ...];
for (var i = 0; i < queries.length; i++)
{
f[i] = function(cb, funcIndex) // sequenty gives you cb and funcIndex
{
db.query(queries[funcIndex], function(err, info)
{
cb(); // must be called
});
}
}
sequenty.run(f); // fire!
Using the request library can help minimize the cruft:
var request = require('request')
request({ uri: 'http://api.com/1' }, function(err, response, body){
// use body
request({ uri: 'http://api.com/2' }, function(err, response, body){
// use body
request({ uri: 'http://api.com/3' }, function(err, response, body){
// use body
})
})
})
But for maximum awesomeness you should try some control-flow library like Step - it will also allow you to parallelize requests, assuming that it's acceptable:
var request = require('request')
var Step = require('step')
// request returns body as 3rd argument
// we have to move it so it works with Step :(
request.getBody = function(o, cb){
request(o, function(err, resp, body){
cb(err, body)
})
}
Step(
function getData(){
request.getBody({ uri: 'http://api.com/?method=1' }, this.parallel())
request.getBody({ uri: 'http://api.com/?method=2' }, this.parallel())
request.getBody({ uri: 'http://api.com/?method=3' }, this.parallel())
},
function doStuff(err, r1, r2, r3){
console.log(r1,r2,r3)
}
)
There are lots of control flow libraries -- I like conseq (... because I wrote it.) Also, on('data') can fire several times, so use a REST wrapper library like restler.
Seq()
.seq(function () {
rest.get('http://www.example.com/api_1.php').on('complete', this.next);
})
.seq(function (d1) {
this.d1 = d1;
rest.get('http://www.example.com/api_2.php').on('complete', this.next);
})
.seq(function (d2) {
this.d2 = d2;
rest.get('http://www.example.com/api_3.php').on('complete', this.next);
})
.seq(function (d3) {
// use this.d1, this.d2, d3
})
This has been answered well by Raynos. Yet there have been changes in the sequence library since the answer has been posted.
To get sequence working, follow this link: https://github.com/FuturesJS/sequence/tree/9daf0000289954b85c0925119821752fbfb3521e.
This is how you can get it working after npm install sequence:
var seq = require('sequence').Sequence;
var sequence = seq.create();
seq.then(function call 1).then(function call 2);
Here's my version of #andy-shin sequently with arguments in array instead of index:
function run(funcs, args) {
var i = 0;
var recursive = function() {
funcs[i](function() {
i++;
if (i < funcs.length)
recursive();
}, args[i]);
};
recursive();
}
...4 years later...
Here is an original solution with the framework Danf (you don't need any code for this kind of things, only some config):
// config/common/config/sequences.js
'use strict';
module.exports = {
executeMySyncQueries: {
operations: [
{
order: 0,
service: 'danf:http.router',
method: 'follow',
arguments: [
'www.example.com/api_1.php',
'GET'
],
scope: 'response1'
},
{
order: 1,
service: 'danf:http.router',
method: 'follow',
arguments: [
'www.example.com/api_2.php',
'GET'
],
scope: 'response2'
},
{
order: 2,
service: 'danf:http.router',
method: 'follow',
arguments: [
'www.example.com/api_3.php',
'GET'
],
scope: 'response3'
}
]
}
};
Use the same order value for operations you want to be executed in parallel.
If you want to be even shorter, you can use a collection process:
// config/common/config/sequences.js
'use strict';
module.exports = {
executeMySyncQueries: {
operations: [
{
service: 'danf:http.router',
method: 'follow',
// Process the operation on each item
// of the following collection.
collection: {
// Define the input collection.
input: [
'www.example.com/api_1.php',
'www.example.com/api_2.php',
'www.example.com/api_3.php'
],
// Define the async method used.
// You can specify any collection method
// of the async lib.
// '--' is a shorcut for 'forEachOfSeries'
// which is an execution in series.
method: '--'
},
arguments: [
// Resolve reference '##.##' in the context
// of the input item.
'##.##',
'GET'
],
// Set the responses in the property 'responses'
// of the stream.
scope: 'responses'
}
]
}
};
Take a look at the overview of the framework for more informations.
I landed here because I needed to rate-limit http.request (~10k aggregation queries to elastic search to build an analytical report). The following just choked my machine.
for (item in set) {
http.request(... + item + ...);
}
My URLs are very simple so this may not trivially apply to the original question but I think it's both potentially applicable and worth writing here for readers that land here with issues similar to mine and who want a trivial JavaScript no-library solution.
My job wasn't order dependent and my first approach to bodging this was to wrap it in a shell script to chunk it (because I'm new to JavaScript). That was functional but not satisfactory. My JavaScript resolution in the end was to do the following:
var stack=[];
stack.push('BOTTOM');
function get_top() {
var top = stack.pop();
if (top != 'BOTTOM')
collect(top);
}
function collect(item) {
http.request( ... + item + ...
result.on('end', function() {
...
get_top();
});
);
}
for (item in set) {
stack.push(item);
}
get_top();
It looks like mutual recursion between collect and get_top. I'm not sure it is in effect because the system is asynchronous and the function collect completes with a callback stashed for the event at on.('end'.
I think it is general enough to apply to the original question. If, like my scenario, the sequence/set is known, all URLs/keys can be pushed on the stack in one step. If they are calculated as you go, the on('end' function can push the next url on the stack just before get_top(). If anything, the result has less nesting and might be easier to refactor when the API you're calling changes.
I realise this is effectively equivalent to the #generalhenry's simple recursive version above (so I upvoted that!)
Super Request
This is another synchronous module that is based off of request and uses promises. Super simple to use, works well with mocha tests.
npm install super-request
request("http://domain.com")
.post("/login")
.form({username: "username", password: "password"})
.expect(200)
.expect({loggedIn: true})
.end() //this request is done
//now start a new one in the same session
.get("/some/protected/route")
.expect(200, {hello: "world"})
.end(function(err){
if(err){
throw err;
}
});
This code can be used to execute an array of promises synchronously & sequentially after which you can execute your final code in the .then() call.
const allTasks = [() => promise1, () => promise2, () => promise3];
function executePromisesSync(tasks) {
return tasks.reduce((task, nextTask) => task.then(nextTask), Promise.resolve());
}
executePromisesSync(allTasks).then(
result => console.log(result),
error => console.error(error)
);
I actually got exactly what you (and me) wanted, without the use of await, Promises, or inclusions of any (external) library (except our own).
Here's how to do it:
We're going to make a C++ module to go with node.js, and that C++ module function will make the HTTP request and return the data as a string, and you can use that directly by doing:
var myData = newModule.get(url);
ARE YOU READY to get started?
Step 1:
make a new folder somewhere else on your computer, we're only using this folder to build the module.node file (compiled from C++), you can move it later.
In the new folder (I put mine in mynewFolder/src for organize-ness):
npm init
then
npm install node-gyp -g
now make 2 new files:
1, called something.cpp and for put this code in it (or modify it if you want):
#pragma comment(lib, "urlmon.lib")
#include <sstream>
#include <WTypes.h>
#include <node.h>
#include <urlmon.h>
#include <iostream>
using namespace std;
using namespace v8;
Local<Value> S(const char* inp, Isolate* is) {
return String::NewFromUtf8(
is,
inp,
NewStringType::kNormal
).ToLocalChecked();
}
Local<Value> N(double inp, Isolate* is) {
return Number::New(
is,
inp
);
}
const char* stdStr(Local<Value> str, Isolate* is) {
String::Utf8Value val(is, str);
return *val;
}
double num(Local<Value> inp) {
return inp.As<Number>()->Value();
}
Local<Value> str(Local<Value> inp) {
return inp.As<String>();
}
Local<Value> get(const char* url, Isolate* is) {
IStream* stream;
HRESULT res = URLOpenBlockingStream(0, url, &stream, 0, 0);
char buffer[100];
unsigned long bytesReadSoFar;
stringstream ss;
stream->Read(buffer, 100, &bytesReadSoFar);
while(bytesReadSoFar > 0U) {
ss.write(buffer, (long long) bytesReadSoFar);
stream->Read(buffer, 100, &bytesReadSoFar);
}
stream->Release();
const string tmp = ss.str();
const char* cstr = tmp.c_str();
return S(cstr, is);
}
void Hello(const FunctionCallbackInfo<Value>& arguments) {
cout << "Yo there!!" << endl;
Isolate* is = arguments.GetIsolate();
Local<Context> ctx = is->GetCurrentContext();
const char* url = stdStr(arguments[0], is);
Local<Value> pg = get(url,is);
Local<Object> obj = Object::New(is);
obj->Set(ctx,
S("result",is),
pg
);
arguments.GetReturnValue().Set(
obj
);
}
void Init(Local<Object> exports) {
NODE_SET_METHOD(exports, "get", Hello);
}
NODE_MODULE(cobypp, Init);
Now make a new file in the same directory called something.gyp and put (something like) this in it:
{
"targets": [
{
"target_name": "cobypp",
"sources": [ "src/cobypp.cpp" ]
}
]
}
Now in the package.json file, add: "gypfile": true,
Now: in the console, node-gyp rebuild
If it goes through the whole command and says "ok" at the end with no errors, you're (almost) good to go, if not, then leave a comment..
But if it works then go to build/Release/cobypp.node (or whatever its called for you), copy it into your main node.js folder, then in node.js:
var myCPP = require("./cobypp")
var myData = myCPP.get("http://google.com").result;
console.log(myData);
..
response.end(myData);//or whatever
Related
I'm unable to update an object upon lifting a sails app. I think something is wrong with scope but I'm not sure.
config/cron.js
const updaterService = require('../api/services/updater');
module.exports.cron = {
Update: {
schedule: '0 * * * * *',
onTick: function () {
console.log('You will see this every second');
updaterService.updateObj();
}
}
};
api/services/updater.js
const rp = require('request-promise');
const testObj = {
data: '',
};
const showData = () => {
sails.log.info(testObj);
};
const updateObj = async () => {
try {
const updateResponse = await rp(updateOpts);
testObj.data = updateResponse.body.data;
sails.log.info(testObj);
} catch (updateErr) {
sails.log.error(`${updateErr.statusCode}: ${updateErr}`);
}
};
What I get when I call showData() is that there is no update to the object (second line). But when updateObj is called from config/cron, it seems like it has been updated though (first line):
{ data: 'some data here' }
{ data: '' }
Firstly, let's look at services with SailsJS, particularly in versions < 1 where they have since been replaced with helpers.
To create a service, there is a strict filenaming convention. Make sure your service's filename ends in Service.js. The first part of this filename will be used as the globally-accessible variable name for the service.
In your example, this would mean renaming the file to UpdaterService.js.
Next, services within Sails export some reusable funtionality. In your example, this will most likely be your function call to updateObj. I'm not sure why you are using request-promise for a scheduled (batch) job, so for extra simplicity, I will just use request. If not already in your library, you will need to install request - npm install request.
This is a complete example service below, I've cut out the stuff that does not make sense to me from your code.
UpdaterService.js.
const request = require('request');
module.exports = {
updateObj : () => {
// I am assuming updateOpts is some URL with serialized params, so let's just call it url
let url = "https://www.example.com?foo=bar";
request(url, function (error, response, body) {
if(error){
sails.log.error(error);
}
if(body && body.data){
sails.log.info(body.data);
}
});
}
};
And here is an example cron file calling the above service every day at 12 midday. No need require the service as it is automatically globally-accessible if named correctly.
config/cron.js
module.exports.cron = {
dailyScheduler: {
schedule: '0 0 12 * * *',
onTick: function() {
UpdaterService.updateObj();
}
}
};
I am trying to call one async function from inside a loop run by another async function. These functions call APIs and I am using request-promise using nodeJS.
functions.js file
const rp = require("request-promise");
// function (1)
async email_views: emailId => {
let data = {};
await rp({
url: 'myapiurl',
qs: { accessToken: 'xyz', emailID: emailId },
method: 'GET'
})
.then( body => { data = JSON.parse(body) })
.catch( error => { console.log(error} );
return data;
};
The above JSON looks like this:
...
data:{
records: [
{
...
contactID: 123456,
...
},
{
...
contactID: 456789,
...
}
]
}
...
I am running a loop to get individual record, where I am getting a contactID associated with each of them.
// function#2 (also in functions.js file)
async contact_detail: contactId => {
let data = {};
await rp({
url: 'myapiurl2',
qs: { accessToken: 'xyz', contactID: contactId },
method: 'GET'
})
.then( body => { data = JSON.parse(body) })
.catch( error => { console.log(error} );
return data;
};
The above function takes one contactId as parameter and gets that contact's detail calling another API endpoint.
Both functions work fine when they are called separately. But I am trying to do it inside a loop like this:
...
const result = await email_views(99999); // function#1
const records = result.data.records;
...
let names = "";
for( let i=0; i<records.length; i++) {
...
const cId = records[i].contactID;
const contact = await contact_detail(cId); // function#2
names += contact.data.firstName + " " + contact.data.lastName + " ";
...
}
console.log(names);
...
The problem is I am only getting the first contact back from the above code block, i.e. even I have 20 records from function#1, in the loop when I am calling contact_detail (function#2) for each contactID (cId), I get contact detail once, i.e. for the first cId only. For rest I get nothing!
What is the correct way to achieve this using nodeJs?
UPDATE:
const { App } = require("jovo-framework");
const { Alexa } = require("jovo-platform-alexa");
const { GoogleAssistant } = require("jovo-platform-googleassistant");
const { JovoDebugger } = require("jovo-plugin-debugger");
const { FileDb } = require("jovo-db-filedb");
const custom = require("./functions");
const menuop = require("./menu");
const stateus = require("./stateus");
const alexaSpeeches = require("./default_speech");
const app = new App();
app.use(new Alexa(), new GoogleAssistant(), new JovoDebugger(), new FileDb());
let sp = "";
async EmailViewsByContactIntent() {
try {
const viewEmailId =
this.$session.$data.viewEmailIdSessionKey != null
? this.$session.$data.viewEmailIdSessionKey
: this.$inputs.view_email_Id_Number.value;
let pageIndex =
this.$session.$data.viewEmailPageIndex != null
? this.$session.$data.viewEmailPageIndex
: 1;
const result = await custom.email_views_by_emailId(
viewEmailId,
pageIndex
);
const records = result.data.records;
if (records.length > 0) {
const totalRecords = result.data.paging.totalRecords;
this.$session.$data.viewEmailTotalPages = totalRecords;
sp = `i have found a total of ${totalRecords} following view records. `;
if (totalRecords > 5) {
sp += `i will tell you 5 records at a time. for next 5 records, please say, next. `;
this.$session.$data.viewEmailIdSessionKey = this.$inputs.view_email_Id_Number.value;
this.$session.$data.viewEmailPageIndex++;
}
for (let i = 0; i < records.length; i++) {
const r = records[i];
/* Here I want to pass r.contactID as contactId in the function contact_detail like this: */
const contact = await custom.contact_detail(r.contactID);
const contact_name = contact.data.firstName + " " + contact.data.lastName;
/* The above two lines of code fetch contact_name for the first r.contactID and for the rest I get an empty string only. */
const formatted_date = r.date.split(" ")[0];
sp += `contact ID ${spellOut_speech_builder(
r.contactID
)} had viewed on ${formatted_date} from IP address ${
r.ipAddress
}. name of contact is, ${contact_name}. `;
}
if (totalRecords > 5) {
sp += ` please say, next, for next 5 records. `;
}
} else {
sp = ``;
}
this.ask(sp);
} catch (e) {
this.tell(e);
}
}
I am building an alexa skill using JOVO framework and nodeJS.
UPDATE #2
As a test, I only returned the contactId which I am passing to the contact_detail function and I am getting the correct value back to the above code under my first UPDATE.
async contact_detail: contactId => {
return contactId;
}
It seems even after getting the value right, the function is somehow failing to execute. However, the same contact_detail function works perfectly OK, when I am calling it from another place. Only doesn't not work inside a loop.
What could be the reason?
I must be missing something but don't know what!
You are mixing async await and promises together which is causing you confusion. You typically would use one of the other(as async await effectivly provides syntax sugar so you can avoid dealing with the verbose promise code) in a given location.
Because you mixed the two you are in a weird area where the behavior is harder to nail down.
If you want to use async await your functions should look like
async contact_detail: contactId => {
try {
const body = await rp({
url: 'myapiurl2',
qs: { ... }
});
return JSON.parse(body);
} catch(e) {
console.log(e);
//This will return undefined in exception cases. You may want to catch at a higher level.
}
};
or with promises
async contact_detail: contactId => {
return rp({
url: 'myapiurl2',
qs: { ... }
})
.then( body => JSON.parse(body))
.catch( error => {
console.log(error);
//This will return undefined in exception cases. You probably dont want to catch here.
});
};
Keep in mind your current code executing the function will do each call in series. If you want to do them in parallel you will need to call the function a bit differently and use something like Promise.all to resolve the result.
Here you go:
...
const result = await email_views(99999); // function#1
const records = result.data.records;
...
let names = "";
await Promise.all(records.map(async record => {
let cId = record.contactID;
let contact = await contact_detail(cId);
names += contact.data.firstName + " " + contact.data.lastName + " ";
});
console.log(names);
...
I'm posting this as an answer only because I need to show you some multi-line code as part of throubleshooting this. Not sure this solves your issue yet, but it is a problem.
Your contact_detail() function is not properly returning errors. Instead, it eats the error and resolves with an empty object. That could be what is causing your blank names. It should just return the promise directly and if you want to log the error, then it needs to rethrow. Also, there's no reason for it to be declared async or to use await. You can just return the promise directly. You can also let request-promise parts the JSON response for you too.
Also, I notice, there appears to be a syntax error in your .catch() which could also be part of the problem.
contact_detail: contactId => {
return rp({
url: 'myapiurl2',
qs: { accessToken: 'xyz', contactID: contactId },
json: true,
method: 'GET'
}).catch( error => {
// log error and rethrow so any error propagates
console.log(error);
throw error;
});
};
Then, you would call this like you originally were (note you still use await when calling it because it returns a promise):
...
const result = await email_views(99999); // function#1
const records = result.data.records;
...
let names = "";
for( let i=0; i<records.length; i++) {
...
const cId = records[i].contactID;
const contact = await contact_detail(cId);
names += contact.data.firstName + " " + contact.data.lastName + " ";
...
}
console.log(names);
...
Cannot load JS file in my app (getting undefined) and I want to emulate the same effect as the tag in the plain HTML.
I have tried
import Api from './api' -> tells me that none of the defined function is a function (don't have any circular dependencies), so my best guess it that Api was not initalized or something?
Tried module.exports on Api -> tells me that Api is undefined
Tried exports.Api -> tells me that the function which i try to call from the Api is not a function
I tried to require and a few more things, which I cannot even recall, and none of it seems to be working. Main issue is that I don't recognize the format of the JS file in question since I never seen a variable declared as a function that contains other functions, so explanation on that might come in handy tbh.
var Api = (function() {
var requestPayload;
var responsePayload;
var messageEndpoint = '/api/message';
var sessionEndpoint = '/api/session';
var sessionId = null;
// Publicly accessible methods defined
return {
sendRequest: sendRequest,
getSessionId: getSessionId,
// The request/response getters/setters are defined here to prevent internal methods
// from calling the methods without any of the callbacks that are added elsewhere.
getRequestPayload: function() {
return requestPayload;
},
setRequestPayload: function(newPayloadStr) {
requestPayload = JSON.parse(newPayloadStr);
},
getResponsePayload: function() {
return responsePayload;
},
setResponsePayload: function(newPayloadStr) {
responsePayload = JSON.parse(newPayloadStr);
},
setErrorPayload: function() {
}
};
function getSessionId(callback) {
var http = new XMLHttpRequest();
http.open('GET', sessionEndpoint, true);
http.setRequestHeader('Content-type', 'application/json');
http.onreadystatechange = function () {
if (http.readyState === XMLHttpRequest.DONE) {
var res = JSON.parse(http.responseText);
sessionId = res.session_id;
callback();
}
};
http.send();
}
// Send a message request to the server
function sendRequest(text, context) {
// Build request payload
var payloadToWatson = {
session_id: sessionId
};
payloadToWatson.input = {
message_type: 'text',
text: text,
};
if (context) {
payloadToWatson.context = context;
}
// Built http request
var http = new XMLHttpRequest();
http.open('POST', messageEndpoint, true);
http.setRequestHeader('Content-type', 'application/json');
http.onreadystatechange = function() {
if (http.readyState === XMLHttpRequest.DONE && http.status === 200 && http.responseText) {
Api.setResponsePayload(http.responseText);
} else if (http.readyState === XMLHttpRequest.DONE && http.status !== 200) {
Api.setErrorPayload({
'output': {
'generic': [
{
'response_type': 'text',
'text': 'Something went wrong.'
}
],
}
});
}
};
var params = JSON.stringify(payloadToWatson);
// Stored in variable (publicly visible through Api.getRequestPayload)
// to be used throughout the application
if (Object.getOwnPropertyNames(payloadToWatson).length !== 0) {
Api.setRequestPayload(params);
}
http.send(params);
}
}());
Code above is provided by IBM (for the Watson Assistant I am trying to work with) and the code is for the Node.JS application which works fine.
It works fine since the code above is simply included in the app through the tag in their index.html and voila, it works, but I don't have that ability (read below).
My issue is that their app is also a client app and I want to transfer all of that 'back-end' stuff to my REST API and that is why I am trying to use the code above.
var Api = (function() {
var messageEndpoint = "/api/message";
// Publicly accessible methods defined
return {
messageEndpoint: messageEndpoint
};
})();
module.exports = Api ;
And you can use it like
const api = require("./api");
console.log(api);
So basically just add module.exports = Api ; in api file and you would be able to use it.
I am trying to use Node.js to programmatically build Jenkins jobs that take Git parameters.
I am sending the parameters as post data, as shown below. However, no matter what value I assign to ref, Jenkins runs the build with the default parameter value (specified in the job's configuration). I have tried passing in the parameters as query strings in the URL, but that also did not work.
I am using Jenkins v1.651.1 and Node v6.2.0.
var jobOptions = {
url: requestedJobObject.url + 'build',
method: 'POST',
port: 8080
};
// parameters = { "name": "ref", "value": "origin/master" }
if (!_.isEmpty(parameters)) {
var jsonParametersString = JSON.stringify({"parameter": parameters});
var parameterParam = encodeURIComponent(jsonParametersString);
parameters.json = parameterParam;
jobOptions.headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': querystring.stringify(parameters).length
};
jobOptions.url += 'WithParameters';
postData = querystring.stringify(parameters);
}
// jobOptions contains auth field & separates url into hostname and path
// makes an http request to jobOptions and calls req.write(postData)
makeRequest(jobOptions, callback, responseCB, postData)
makeRequest makes an http request:
function makeRequest (object, callback, responseCB, postData) {
var accumulator = '';
var parsedUrl = u.parse('//' + object.url, true, true);
var options = {
hostname: parsedUrl.hostname,
port: object.port || 8080,
path: parsedUrl.path,
method: object.method || 'GET',
auth: getAuthByHost(parsedUrl.hostname)
};
if (object.headers) {
options.headers = object.headers;
}
var response = null;
var req = http.request(options, function(res) {
response = res;
res.on('data', function (data) {
accumulator = accumulator + data.toString();
res.resume();
});
res.on('close', function () {
// first assume accumulator is JSON object
var responseContent;
try {
responseContent = JSON.parse(accumulator);
}
// if not object, use accumulator as string
catch (err) {
responseContent = accumulator;
}
callback(responseContent, response.statusCode);
if (responseCB) {
responseCB(res);
}
});
});
req.on('close', function () {
// first assume accumulator is JSON object
var responseContent;
try {
responseContent = JSON.parse(accumulator);
}
catch (err) {
responseContent = accumulator;
}
callback(responseContent, response.statusCode);
if (responseCB) {
responseCB(response);
}
});
if (postData) {
req.write(postData);
}
req.end();
}
try this, it works for me:
var auth = 'Basic yourUserToken';
var jobOptions = {
url:'jenkinsHostName:8080/jenkins/job/jobName/' +'build',
method: 'POST',
port: 8080
};
var parameter = {"parameter": [{"name":"ref", "value":"origin/master"}]};
var postData;
if (!_.isEmpty(parameter)) {
var jsonParametersString = JSON.stringify(parameter);
jobOptions.headers = {
'Authorization':auth,
'Content-Type': 'application/x-www-form-urlencoded',
};
jobOptions.url += '?token=jobRemoteTriggerToken';
postData = "json="+jsonParametersString;
console.log("postData = " + postData);
}
var callback;
var responseCB;
makeRequest(jobOptions, callback, responseCB, postData) ;
It is based on your code. I removed the querystring - it seems that it returned an empty string when performed on the parameters object. I change /buildWithParameters to /build - it didn't work the other way.
In addition, verify that when you pass the 'Content-Length' in the header, it doesn't truncated your json parameters object (I removed it ).
also note that I used the user API token, that you can get at http://yourJenkinsUrl/me/configure and click the "Shown API Token" button.
Not sure about this, as I don't know Node.js -- but maybe this fits: the Jenkins remote access API indicates that the parameter entity in the json request must point to an array, even if there's just one parameter to be defined.
Does the change below fix the problem (note the angle brackets around parameters)?
[...]
var jsonParametersString = JSON.stringify({"parameter": [parameters]});
[...]
I would love to write a generic wrapper that takes a function, and returns the "async-style" version of that function IF it wasn't async to start with.
Trouble is, there is no easy way to know whether the call is sync or async. So... this basically "cannot be done". Right?
(Note that the wrapper should harmonise sync functions to async style, and LEAVE async functions alone)
var wrapper = function( fn ){
return function(){
var args = Array.prototype.splice.call(arguments, 0);
var cb = args[ args.length - 1 ];
// ?!?!?!?!?
// I cannot actually tell if `fn` is sync
// or async, and cannot determine it!
console.log( fn.toString() );
}
}
var f1Async = wrapper( function( arg, next ){
next( null, 'async' + arg );
})
var f2Sync = wrapper( function( arg ){
return 'sync' + arg;
})
f1Async( "some", function(err, ret ){
console.log( ret );
});
f2Sync( "some other", function(err, ret ){
console.log( ret );
});
You cannot find out what the accepted arguments of a function are, so you cannot find out if it takes a callback or not.
In javascript there is no way to check if the last argument of a function is a function, because in javascript you do not define the types of your arguments.
My solution works by getting a list of the parameters in the function, then using a RegExp to see if that parameter is used as a function. Also, in the case that the callback is not being used directly (like passing it to something else), it has a list of argument names to be considered as a callback.
And the code is:
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
var CALLBACK_NAMES = [ "next", "done", "callback", "cb"];
function getParamNames(func) {
var fnStr = func.toString().replace(STRIP_COMMENTS, '')
var result = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')')).match(/([^\s,]+)/g)
if(result === null)
result = []
return result
}
function checkIfParamIsFunction(func, paramName){
var fnStr = func.toString();
if (fnStr.replace(new RegExp("(" + paramName + "\s*\([A-Za-z0-9,\.]*\)?!{|" + paramName + ".apply\([A-Za-z0-9,\.]*\)|" + paramName + ".call\([A-Za-z0-9,\.]*\))", ""), "{<._|/}") != fnStr) { // Remove All Calls to the arg as a function, then check to see if anything was changed.
return true;
} else {
return false;
}
}
function makeAsync(func) {
var paramNames = getParamNames(func);
if (checkIfParamIsFunction(func, paramNames[paramNames.length - 1])
|| CALLBACK_NAMES.indexOf(paramNames[paramNames.length - 1]) != -1) {
// Function Is Already async
return func;
} else {
return function () {
var args = Array.prototype.slice.call(arguments);
var cb = args.pop();
cb(func.apply(this, args));
}
}
}
function test1(a){
return (a+' test1');
};
function test2(a, callback){
return callback(a+' test2')
};
function main(){
var tested1 = makeAsync(test1);
var tested2 = makeAsync(test2);
tested1('hello', function(output){
console.log('holy shit it\'s now async!!');
console.log(output);
});
tested2('world', function(output){
console.log('this one was already async tho');
console.log(output);
});
}
main();
Simply call makeAsync(function) and it will return an async function. This will work if you use function.apply or .call.
It simply cannot be done. End of story.
Though, this is not the answer, but a good alternative. I have provided example of browser based JavaScript but same class can be used on Node as well.
To solve this problem, promises were developed. However we use a modified version of promise as follow.
function AtomPromise(f)
{
// Success callbacks
this.sc = [];
// Error callbacks
this.ec = [];
this.i = f;
this.state = 'ready';
}
AtomPromise.prototype ={
finish: function(s,r) {
this.result = r;
var c = s ? this.sc : this.ec;
this.state = s ? 'done' : 'failed' ;
for(var i=o ; i< c.length; i++){
c[i](this);
}
},
invoke: function(f) {
this.state = 'invoking';
this.i(this);
},
then: function(f) {
this.sc.push(f);
},
failed: function(f){
this.ec.push(f);
},
value: function(v) {
if(v !== undefined ) this.result = v;
return this.result;
},
pushValue: function(v) {
var _this = this;
setTimeout(100, function () {
_this.finish(true, v);
});
}
}
//wrap jQuery AJAX
AtomPromise.ajax = function( url, options ) {
return new AtomPromise(function (ap){
$.ajax(url, options)
.then( function(r){ ap.finish(true, r); })
.failed( function (){ ap.finish( false, arguments) });
}) ;
}
//Wrape sync function
AtomPromise.localStorage = function( key ) {
return new AtomPromise(function (ap){
var v = localStorage[key];
ap.pushValue(v);
}) ;
}
// Calling Sequence
AtomPromise.ajax( 'Url' ).then( function(ap) {
alert(ap.value());
}).invoke();
AtomPromise.localStorage( 'Url' ).then( function(ap) {
alert(ap.value());
}).invoke();
Both functions are now asynchronous. Push Value method makes result route through setTimeout that makes further calls asynchronous.
This is used in Web Atoms JS to wrape async code into single attribute and by following one pattern you can get rid of async callback hell. http://webatomsjs.neurospeech.com/docs/#page=concepts%2Fatom-promise.html
Disclaimer: I am author of Web Atoms JS.