I'm Building an auto dialer with Twilio.Device Js - javascript

I'm building an auto dialer with twilio and I'm a litle bit stuck . I succed with getting 2 outbound calls from my browser and when the first hangs up or busy the second one starts . but I want to implement it for many calls from an array of phones in my sql Db .
here is my code - I'm starting with connecting to twilio and getting a token after that I'm making an array of phones using an AJAX call to my db with php code , Then I'm making a $GetJson request for the first phone and the next phone (if exist) from the Db and finally I'm starting a twilio.device.connect and when the first disconnect the second's starts:
status_progress.php
/********TWILIO **********/
$.getJSON('../../twilio/token.php').done(function (data) {
console.log('Token: ' + data.token);
console.log('Identity: ' + data.identity);
// Setup Twilio.Device
Twilio.Device.setup(data.token);
Twilio.Device.ready(function (device) {
console.log('Twilio.Device Ready!');
});
Twilio.Device.error(function (error) {
console.log('Twilio.Device Error: ' + error.message);
});
Twilio.Device.connect(function (conn) {
console.log('Successfully established call!');
});
Twilio.Device.disconnect(function (conn) {
console.log('Call ended.');
});
Twilio.Device.incoming(function (conn) {
console.log('Incoming connection from ' + conn.parameters.From);
var archEnemyPhoneNumber = '+12099517118';
if (conn.parameters.From === archEnemyPhoneNumber) {
conn.reject();
console.log('It\'s your nemesis. Rejected call.');
} else {
// accept the incoming connection and start two-way audio
conn.accept();
}
});
//setClientNameUI(data.identity);
})
.fail(function () {
console.log('Could not get a token from server!');
});
// Bind button to hangup call
document.getElementById('closeCall').onclick = function () {
console.log('Hanging up...');
Twilio.Device.disconnectAll();
};
$("#phoneThem").live("click", function() {
var arr = [];
var i =0;
$('.checkDeal:checked').each(function () {
arr[i++] = $(this).val();
});
var arr_value = JSON.stringify(arr);
var conected = 'no';
$.ajax({
type: "POST",
url: 'make_call_list.php',
data: {arr_checked : arr_value}, // serializes the form's elements.
success: function(calls)
{
$.getJSON('make_a_call.php').done(function (data) {
var arr = data.arr;
var params = {
To: data.phone + '-' + data.crm
};
console.log('Calling ' + params.To + '...' );
var connection = Twilio.Device.connect(params);
connection.on('disconnect', function(conn) {
console.log("the call has ended");
var params2 = {
To: data.phone_next + '-' + data.crm
};
console.log('Calling ' + params2.To + '...' );
var connection_2 = Twilio.Device.connect(params2);
});
});
}, // SUCCES MAKE A CALL LIST
});
});
this is the twiml code :
<?php
require_once 'twilio-php-master/Twilio/autoload.php';
use Twilio\TwiML\VoiceResponse;
$break = $_POST['To'];
$break_arr = explode('-',$break);
$prefix = '+972';
$response = new VoiceResponse();
$dial = $response->dial('', ['callerId' => '+972 50-225-8234']);
$break_arr[0] = ltrim($break_arr[0] , '0');
$break_arr[0] = $prefix.$break_arr[0] ;
$dial->number($break_arr[0], ['statusCallbackEvent' => 'initiated
ringing answered completed',
'statusCallback' =>
'https://crm.unic.co.il/crm_test_zone/handleDialCallStatus.php?
crm='.$break_arr[1].'',
'statusCallbackMethod' => 'POST']);
echo $response;
?>

Twilio developer evangelist here.
Rather than write out the code to sequentially dial all of the numbers, you can use the callback functions within the Twilio Device to trigger the next call to happen.
What I would do is load all of the numbers you want to dial into the front end in an array.
Then, I would create a function that takes two arguments; the current index of the number you are dialling and the array of numbers. The function would check to make sure the current index is still within the array and if it isn't return (all numbers have been dialled). Otherwise, it would generate a call to the number in the array. It would also set up a listener for the disconnection event for that call. When it disconnects, it calls the same function again, but moves the index on 1.
Finally, call that function with the initial index of 0 and the array of numbers. Something a bit like this:
const array = getArrayOfNumbers();
function dialNumber(index, array) {
if (index > array.length) return;
const connection = Twilio.Device.connect({ number: array[index] });
connection.on('disconnected', function() {
dialNumber(index+1, array);
});
}
dialNumber(0, array);
Let me know if that helps at all.

Related

How can I add a setTimeOut() to run a function and insert data multiple times?

A few days ago I did a project and I had some problems, which where solved in this question, let me try to resume it.
I need to insert multiple objects into a DB in SQLServer, for that, I did a function that loops another function, which opens a connection, inserts and closes the connection, then, repeats it over and over again.
It worked fine, till today that was tested in a collegue PC, in the server of the job, I get this error:
Error: Requests can only be made in the LoggedIn state, not the LoggedInSendingInitialSql state
Error: Requests can only be made in the LoggedIn state, not the SentLogin7WithStandardLogin state
Here's the code we tested (the same in my last question), it works in my PC, but not in the other:
var config = {
...
};
function insertOffice(index) {
var connection = new Connection(config);
connection.on("connect", function (err) {
console.log("Successful connection");
});
connection.connect();
let url = `https://api.openweathermap.org/data/2.5/weather?lat=${offices[index].latjson}&lon=${offices[index].lonjson}&appid=${api_key}&units=metric&lang=sp`;
fetch(url)
.then((response) => { return response.json(); })
.then(function (data) {
var myObject = {
Id_Oficina: offices[index].IdOficina,
...
};
const request = new Request(
"EXEC USP_BI_CSL_insert_reg_RegistroTemperaturaXidOdicina #IdOficina, ...",
function (err) {
if (err) {
console.log("Couldnt insert data (" + index + "), " + err);
} else {
console.log("Data with ID: " + myObject.Id_Oficina +" inserted succesfully(" + index + ").")
}
}
);
request.addParameter("IdOficina", TYPES.SmallInt, myObject.Id_Oficina);
...
request.on("row", function (columns) {
columns.forEach(function (column) {
if (column.value === null) {
console.log("NULL");
} else {
console.log("Product id of inserted item is " + column.value);
}
});
});
request.on("requestCompleted", function () {
connection.close();
});
connection.execSql(request);
});
}
function functionLooper() {
for (let i = 0; i < offices.length; i++) {
let response = insertOffice(i);
}
}
functionLooper();
So, I thought it would be a good idea to use a setTimeOut, to:
Run functionLooper().
Open connection, insert and close.
Wait a few seconds.
Repeat.
So, I changed to this:
setTimeout(functionLooper, 2000);
function functionLooper() {
for (let i = 0; i < offices.length; i++) {
let response = insertOffice(i);
}
}
It works, but, as you can see, only waits when I first run it, so tried to make a function that runs setTimeout(functionLooper, 2000); like functionLooper() does, but it didn't work either.
function TimerLooper() {
for (let i = 0; i < offices.length; i++) {
setTimeout(functionLooper, 500);
}
}
function functionLooper() {
for (let i = 0; i < offices.length; i++) {
let response = insertOffice(i);
}
}
TimerLooper();
This shows me this error:
Error: Validation failed for parameter 'Descripcion'. No collation was set by the server for the current connection.
file:///...:/.../.../node_modules/node-fetch/src/index.js:95
reject(new FetchError(request to ${request.url} failed, reason: ${error.message}, 'system', error));
^ FetchError: request to https://api.openweathermap.org/data/2.5/weather?lat=XX&lon=XX&appid=XX&units=metric&lang=sp failed, reason: connect ETIMEDOUT X.X.X.X:X
So, I have some questions
How can I use properly setTimeOut? I did this function based on what I watch here in SO, but I just can't get it and I don't know what I'm doing wrong.
Why it works in my PC and the other don't? Do we have to change some kind of config or something?
Using setTimeOut, is the correct way to solve this problem? if not, what would you suggest me?
Could you do something like:
//edit: not disconnect but end
connection.on("end", function(){
functionLopper(index++)
})
function functionLooper(i) {
if(i<offices.length) insertOffice(i)
}
Edit: according to tidious doc
There is an end event emitted on connection.close()
Event: 'end'
function () { }
The connection has ended. This may be as a result of the client calling close(), the server closing the connection, or a network error.
My suggestion from above
var config = {
...
};
function insertOffice(index) {
var connection = new Connection(config);
connection.on("connect", function (err) {
console.log("Successful connection");
});
connection.connect();
let url = `...`;
fetch(url)
.then((response) => { return response.json(); })
.then(function (data) {
...
});
connection.on("end", function(){
functionLopper(index++)
})
}
function functionLooper(i) {
if(i<offices.length) insertOffice(i)
}
``

Throw custom timeout exception

I have a Google Apps Script web app ("Web App") that executes as the user, then calls individual functions from another Apps Script project ("API Executable") via the Apps Script API using UrlFetchApp.fetch() and executes them as me (see Get user info when someone runs Google Apps Script web app as me).
A limitation of this method is that UrlFetchApp.fetch() has a 60s timeout, and one of my functions often takes longer than this. The API Executable function finishes running successfully, but the web app throws a timeout exception. I would like to handle this exception by running a second "follow-up" function that finds and returns the URL of the Google Sheet successfully created by the original function. However, I'll need to pass the follow-up function one of the parameters passed to the original function, and it appears I can't do this within a standard try...catch block.
My idea was to throw an exception that contains the needed parameter, but I can't figure out how to throw my own timeout exception; since Google Apps Script is synchronous, there's no way to track how long UrlFetchApp.fetch() has been running while it's running.
Is there a way to throw your own timeout exception? Or, is there another way I can pass the needed parameter to a function that executes if there's a timeout error?
I tagged Javascript in this post as well since there's a lot of overlap with Google Apps Script and I figured it would improve my chance of connecting with someone who has an answer--hope that's okay. Below is the function I'm using in my web app to call my API Executable functions, in case that's helpful.
EDIT: Based on #TheMaster's comment, I decided to write the script as though parameters passed to executeAsMe() WERE being passed to the catch() block, to see what happened. I expected an exception regarding the fact the opt_timeoutFunction was undefined, but strangely it looks like only the first line of the catch() block is even running, and I'm not sure why.
function executeAsMe(functionName, paramsArray, opt_timeoutFunction, opt_timeoutParams) {
try {
console.log('Using Apps Script API to call function ' + functionName.toString() + ' with parameter(s) ' + paramsArray.toString());
var url = 'https://script.googleapis.com/v1/scripts/Mq71nLXJPX95eVDFPW2DJzcB61X_XfA8E:run';
var payload = JSON.stringify({"function": functionName, "parameters": paramsArray, "devMode": true})
var params = {method:"POST",
headers: {Authorization: 'Bearer ' + getAppsScriptService().getAccessToken()},
payload:payload,
contentType:"application/json",
muteHttpExceptions:true};
var results = UrlFetchApp.fetch(url, params);
var jsonResponse = JSON.parse(results).response;
if (jsonResponse == undefined) {
var jsonResults = undefined;
} else {
var jsonResults = jsonResponse.result;
}
} catch(error) {
console.log('error = ' + error); // I'm seeing this in the logs...
console.log('error.indexOf("Timeout") = ' + error.indexOf("Timeout").toString); // ...but not this. It skips straight to the finally block
if (error.indexOf('Timeout') > 0) { // If error is a timeout error, call follow-up function
console.log('Using Apps Script API to call follow-up function ' + opt_timeoutFunction.toString() + ' with parameter(s) ' + paramsArray.toString());
var url = 'https://script.googleapis.com/v1/scripts/Mq71nLXJPX95eVDFPW2DJzcB61X_XfA8E:run';
var payload = JSON.stringify({"function": opt_timeoutFunction, "parameters": opt_timeoutParams, "devMode": true})
var params = {method:"POST",
headers: {Authorization: 'Bearer ' + getAppsScriptService().getAccessToken()},
payload:payload,
contentType:"application/json",
muteHttpExceptions:true};
var results = UrlFetchApp.fetch(url, params);
var jsonResponse = JSON.parse(results).response;
if (jsonResponse == undefined) {
var jsonResults = undefined;
} else {
var jsonResults = jsonResponse.result;
}
}
} finally {
console.log('jsonResults = ' + jsonResults);
return jsonResults;
}
}
I ended up using the '''catch()''' block to throw an exception back to the client side and handle it there.
Google Apps Script:
function executeAsMe(functionName, paramsArray) {
try {
console.log('Using Apps Script API to call function ' + functionName.toString() + ' with parameter(s) ' + paramsArray.toString());
var url = 'https://script.googleapis.com/v1/scripts/Mq71nLXJPX95eVDFPW2DJzcB61X_XfA8E:run';
var payload = JSON.stringify({"function": functionName, "parameters": paramsArray, "devMode": true})
var params = {method:"POST",
headers: {Authorization: 'Bearer ' + getAppsScriptService().getAccessToken()},
payload:payload,
contentType:"application/json",
muteHttpExceptions:true};
var results = UrlFetchApp.fetch(url, params);
var jsonResponse = JSON.parse(results).response;
if (jsonResponse == undefined) {
var jsonResults = undefined;
} else {
var jsonResults = jsonResponse.result;
}
return jsonResults;
} catch(error) {
console.log('error = ' + error);
if (error.toString().indexOf('Timeout') > 0) {
console.log('Throwing new error');
throw new Error('timeout');
} else {
throw new Error('unknown');
}
} finally {
}
}
Client-side Javascript (a simplified version):
function createMcs() {
var userFolder = getDataFromHtml().userFolder;
google.script.run
.withSuccessHandler(createMcsSuccess)
.withFailureHandler(createMcsFailure)
.withUserObject(userFolder)
.executeAsMe('createMasterCombinedSchedule', [userFolder]);
}
function createMcsSuccess(mcsParameter) {
if (mcsParameter == undefined) {
simpleErrorModal.style.display = "block"; // A generic error message
} else {
document.getElementById("simpleAlertHeaderDiv").innerHTML = 'Master Combined Schedule Created Successfully';
document.getElementById("simpleAlertBodyDiv").innerHTML = 'Your Master Combined Schedule was created successfully. Click here to view.';
simpleAlertModal.style.display = "block";
}
}
function createMcsFailure(mcsError, userFolder, counter) { // The exception I threw will activate this function
if (!counter) { // Added a counter to increment every time checkForCreatedMcs() runs so it doesn't run indefinitely
var counter = 0;
}
if (mcsError.message == 'Error: timeout' && counter < 5) { // If timeout error, wait 10s and look for MCS URL
window.setTimeout(checkForCreatedMcs(mcsError, userFolder, counter), 10000);
} else if (mcsError.message == 'Error: timeout' && counter == 5) { // If it hasn't worked after 5 tries, show generic error message
simpleErrorModal.style.display = "block";
} else { // For any error that's not a timeout exception, show generic error message
simpleErrorModal.style.display = "block";
}
}
function checkForCreatedMcs(mcsError, userFolder, counter) {
counter++;
google.script.run
.withSuccessHandler(checkForCreatedMcsSuccess)
.withUserObject([mcsError, userFolder, counter])
.executeAsMe('checkIfMcsExists', [userFolder]); // checkIfMcsExists() is a pre-existing function in my API Executable project I had already used elsewhere
}
function checkForCreatedMcsSuccess(mcsExistsParameter, params) {
var mcsError = params[0];
var userFolder = params[1];
var counter = params[2];
if (mcsExistsParameter == undefined) { // If function returns undefined, show generic error message
simpleErrorModal.style.display = "block";
} else if (mcsExistsParameter == false) { // If function returns false, wait 10s and try again
createMcsFailure(mcsError, userFolder, counter);
} else { // If function returns URL, show success modal with link
document.getElementById("simpleAlertHeaderDiv").innerHTML = 'Master Combined Schedule Created Successfully';
document.getElementById("simpleAlertBodyDiv").innerHTML = 'Your Master Combined Schedule was created successfully. Click here to view.';
simpleAlertModal.style.display = "block";
}
}
I am sure there has to be a tidier/less complex way to do this, but this worked!

mongodb query inside node js eachOf loop

I want to add have nested DB query inside a eachOf loop which should be synchronous. Tried so many combinations and which but nothing works for inside of foreach loop.
async.eachOf(nc.virtual_devices, function (vd) {
///////// This code work fine /////////////
var domain = extractDomain(vd.api_url);
vd.raw_api_ip = vd.api_ip;
vd.api_ip = getProxiedPath(vd.api_ip);
vd.raw_api_url = vd.api_url;
vd.api_url = nconf.get('PROXIED_PATH_SCHEME') + vd.api_ip + vd.api_url.split(domain)[1];
// Path to websocket notifications
vd.ws_url = nconf.get('PROXIED_PATH_SCHEME') + vd.api_ip + vd.notification_base_uri;
//// THIS CODE IS NOT FINE //////////////
if (nc.type === 'XXX'){
var promise = new Promise (function (resolve,reject) {
console.log("********XX VD TYPE **********");
console.log(JSON.stringify(vd));
console.log("VD ID VALUE IS ", vd.id);
var newID = (vd.id).replace(/\d_/, "");
console.log("VD ID VALUE IS ", newID);
var _idofSubgroup;
var labeltoSearch = nc.type + ' ' + nc.version;
pattern = "/^" + newID + "/i";
test = _idofSubgroup;
pattern = newID;
console.log(pattern);
db.collection('subgroups').findOne({label: labeltoSearch}, function (err, result) {
console.log(result._id);
_idofSubgroup = result._id;
db.collection('exploreposts').find({subgroup: result.id_}, {title: {"$regex": pattern}}).toArray(function (err, results) {
console.log(results);
})
});
})
}
});
Tried with promise inside it but that also in pain.
This is my tried code which is not working fine. Any suggestion would be appreciated., as simply said i have been stuck in callback hell
async.eachOf(nc.virtual_devices, function (vd) {
///////// This code work fine /////////////
var domain = extractDomain(vd.api_url);
vd.raw_api_ip = vd.api_ip;
vd.api_ip = getProxiedPath(vd.api_ip);
vd.raw_api_url = vd.api_url;
vd.api_url = nconf.get('PROXIED_PATH_SCHEME') + vd.api_ip + vd.api_url.split(domain)[1];
// Path to websocket notifications
vd.ws_url = nconf.get('PROXIED_PATH_SCHEME') + vd.api_ip + vd.notification_base_uri;
//// THIS CODE IS NOT FINE with promises also //////////////
if (nc.type === 'XXX'){
var promise = new Promise (function (resolve,reject) {
console.log("********XX VD TYPE **********");
console.log(JSON.stringify(vd));
console.log("VD ID VALUE IS ", vd.id);
var newID = (vd.id).replace(/\d_/, "");
console.log("VD ID VALUE IS ", newID);
var _idofSubgroup;
var labeltoSearch = nc.type + ' ' + nc.version;
pattern = "/^" + newID + "/i";
test = _idofSubgroup;
pattern = newID;
console.log(pattern);
db.collection('subgroups').findOne({label: labeltoSearch}, function (err, result) {
console.log(result._id);
_idofSubgroup = result._id;
resolve ({id_:_idofSubgroup,pattern1 : pattern});
})
});
promise.then (function(result) {
console.log(result.id_);
console.log(result.pattern1);
db.collection('exploreposts').find({subgroup: result.id_}, {title: {"$regex": result.pattern1}}).toArray(function (err, results) {
console.log(results);
})
},function (err){
console.log (err);
})
}
});
It doesn't seem you need to use async.eachOf, but async.each() or async.eachSeries().
This is untested but it would look something like
async.eachSeries(nc.virtual_devices, function iteratee(vd, cb) {
console.log('calling iteratee()')
var domain = extractDomain(vd.api_url);
vd.raw_api_ip = vd.api_ip;
vd.api_ip = getProxiedPath(vd.api_ip);
vd.raw_api_url = vd.api_url;
vd.api_url = nconf.get('PROXIED_PATH_SCHEME') + vd.api_ip + vd.api_url.split(domain)[1];
// Path to websocket notifications
vd.ws_url = nconf.get('PROXIED_PATH_SCHEME') + vd.api_ip + vd.notification_base_uri;
// skip the rest if type is XXX;
// you need to explicitedly call the original callback i.e. cb
// note the use of return to prevent execution of the rest of the code
if (nc.type !== 'XXX')
return cb(null); // or cb();
console.log("********XX VD TYPE **********");
console.log(JSON.stringify(vd));
console.log("VD ID VALUE IS ", vd.id);
var newID = (vd.id).replace(/\d_/, "");
console.log("VD ID VALUE IS ", newID);
// I have no idea what is going here
var _idofSubgroup;
var labeltoSearch = nc.type + ' ' + nc.version;
var pattern = "/^" + newID + "/i";
test = _idofSubgroup;
pattern = newID;
console.log(pattern);
// we're going to use waterfall here as you have 2 async operations, where one is dependent on the other
async.waterfall([
function getSubgroup(cb1) {
console.log('calling getSubgroup')
db.collection('subgroups').findOne({ label: labeltoSearch }, function (err, subgroup) {
// if an error occurs, stop waterfall-loop
// you do this by passing the error in the callback
// again note the use of return here to prevent execution of the rest of the code
if (err) return cb1(err);
// pass the data to the next task
cb1(null, subgroup, pattern);
});
},
function getPosts(subgroup, pattern, cb2) {
// we will only get here if the last task ^ went through
console.log('calling getPosts')
db.collection('exploreposts').find({ subgroup: subgroup._id, title: { $regex: pattern }}).toArray(function (err, posts) {
// if an error occurs, stop waterfall-loop
if (err) return cb2(err);
// do something with posts
console.log('posts', posts);
// otherwise, keep going
// since there are no more waterfall-tasks, waterfall ends
cb2();
});
}
], function (err) {
console.log('waterfall() done');
// if an error occurred during the waterfall-loop, it will come down here
// we will let the original callback i.e. cb deal with this error though
if (err) return cb(err);
// otherwise we're done
// we will let the original callback know everything went well by calling it without any error
cb();
});
// you could have also simply do
// ], cb);
}, function (err) {
console.log('eachSeries() done');
// handle any error that came
console.log(err);
// send response
});
I purposely name the variables and functions so that you get an idea.
Follow the logs if there are any issues.

How to make a function wait for call back, before continuing

In my chat program, I am trying to create a function, that checks if there exists a conversation in the database. If a conversation exists with peopleName it should get retrieved on the client. If a conversation with that name does NOT exist, a new conversation should get created.
It seems that the 'checkConversation' function is not waiting for the result, because it is creating a new conversation every time, even though the conversation exists.
Client-side:
//Starting conversation
$("#people").on("click", ".list-group-item", function() {
var peopleName = $(this).children("span").text();
var peopleID = $(this).children("span").attr("class");
var conversationExists = false;
socket.emit("checkConversation", peopleName, function(data) {
conversationExists = data.result;
if (conversationExists) {
console.log("Retrieved existing conversation with ", peopleName);
return;
// Check if there is a conversation in the Database where this name is conversationReceiver. ------------------------------------
// if there is: retrieve conversation/messages
// else: create conversation.
} else {
console.log("NEW conversation with ", peopleName);
socket.emit("serverCreateConversation", peopleName, peopleID);
$("#msg").prop("readonly", false);
$("#msg").attr("placeholder", "Your message");
$("#send").attr("disabled", false);
$("#chat").empty();
}
});
});
Server-side:
client.on("checkConversation", function(peopleName, fn) {
var match = false;
connection.query("SELECT * FROM `conversations` WHERE `conversation_receiver` = '" + peopleName + "'", function(error, results, fields) {
if (error) {
console.log(error);
} else if (results) {
console.log("Conversation exists!", results);
match = true;
} else {
console.log(fields);
}
});
console.log("match: " + match);
fn({ result: match });
});
This seems like the usual asynchronous callback hurdle people stumble upon when starting with Node.js and other Javascript asynchrony.
Server-side, you need to only call fn when you get the result from the database, i.e. in the callback you pass to connection.query:
client.on("checkConversation", function(peopleName, fn) {
connection.query("SELECT * FROM `conversations` WHERE `conversation_receiver` = '" + peopleName + "'", function(error, results, fields) {
if (error) {
console.error(error);
fn({ result: false, error: true });
return;
}
var match = !!results; // (shorthand to turn a truthy value into true/false)
console.log("Conversation with " + peopleName + ": " + match);
fn({ result: match });
});
});
(I took the liberty of simplifying the code a little, too.)
However, there's another pressing issue: Your code is vulnerable to SQL injection attacks. Please find out how parametrized queries work in the SQL library you're using, and use them instead of building SQL queries with +!
You're doing it wrong, websocket don't work like AJAX, you need to emit the result from your backend, and listen to it on your frontend
you need a socket.emit on your server code
and you need a socket.on on your client code

node.js looping through GETs with promise

I'm new to promises and I'm sure there's an answer/pattern out there but I just couldn't find one that was obvious enough to me to be the right one. I'm using node.js v4.2.4 and https://www.promisejs.org/
This should be pretty easy I think...I need to do multiple blocks of async in a specific order, and one of the middle blocks will be looping through an array of HTTP GETs.
//New Promise = asyncblock1 - FTP List, resolve the returned list array
//.then(asynchblock2(list)) - loop through list array and HTTP GET needed files
//.then(asynchblock3(list)) - update local log
I tried creating a new Promise, resolving it, passing the list to the .then, doing the GET loop, then the file update. I tried using a nested promise.all inside asynchblock2, but it's actually going in reverse order, 3, 2, and 1 due to the timing of those events. Thanks for any help.
EDIT: Ok, this is the pattern that I'm using which works, I just need a GET loop in the middle one now.
var p = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('2 sec');
resolve(1);
},
2000);
}).then(() => {
return new Promise((resolve) => {
setTimeout(() => {
console.log('1.5 sec');
// instead of this section, here I'd like to do something like:
// for(var i = 0; i < dynamicarray.length; i++){
// globalvar[i] = ftpclient.getfile(dynamicarray[i])
// }
// after this loop is done, resolve
resolve(1);
},
1500);
});
}).then(() => {
return new Promise((resolve) => {
setTimeout(() => {
console.log('1 sec');
resolve(1);
},
1000);
});
});
EDIT Here is the almost working code!
var pORecAlert = (function(){
var pa;
var newans = [];
var anstodownload = [];
var anfound = false;//anfound in log file
var nexttab;
var lastchar;
var po;
var fnar = [];
var antext = '';
//-->> This section works fine; it's just creating a JSON object from a local file
try{
console.log('trying');
porfile = fs.readFileSync('an_record_files.json', 'utf8');
if(porfile == null || porfile == ''){
console.log('No data in log file - uploaded_files_data.json being initialized!');
plogObj = [];
}
else{
plogObj = JSON.parse(porfile);
}
}
catch(jpfp){
console.log('Error parsing log file for PO Receiving Alert: ' + jpfp);
return endPORecAlertProgram();
};
if((typeof plogObj) === 'object'){
console.log('an_record_files.json log file found and parsed for PO Receiving Alert!');
}
else{
return mkError(ferror, 'pORecAlert');
};
//finish creating JSON Object
pa = new Client();
pa.connect(ftpoptions);
console.log('FTP Connection for FTP Check Acknowledgement begun...');
pa.on('greeting', function(msg){
console.log('FTP Received Greeting from Server for ftpCheckAcknowledgement: ' + msg);
});
pa.on('ready', function(){
console.log('on ready');
//START PROMISE LIST
var listpromise = new Promise((reslp, rejlp) => {
pa.list('/public_html/test/out', false, (cerr, clist) => {
if(cerr){
return mkError(ferror, 'pORecAlert');
}
else{
console.log('Resolving clist');
reslp(clist);
}
});
});
listpromise.then((reclist) => {
ftpplist:
for(var pcl = 0; pcl < reclist.length; pcl++){
console.log('reclist iteration: ' + pcl);
console.log('checking name: ', reclist[pcl].name);
if(reclist[pcl].name.substring(0, 2) !== 'AN'){
console.log('Not AN - skipping');
continue ftpplist;
}
else{//found an AN
for(var plc = 0; plc < plogObj.length; plc++){
if(reclist[pcl].name === plogObj[plc].anname){
//console.log('Found reclist[pcl].name in local log');
anfound = true;
};
};
if(anfound === false){
console.log('Found AN file to download: ', reclist[pcl].name);
anstodownload.push(reclist[pcl].name);
};
};
};
console.log('anstodownload array:');
console.dir(anstodownload);
return anstodownload;
}).then((fnar) => {
//for simplicity/transparency, here is the array being overwritten
fnar = new Array('AN_17650_37411.699.txt', 'AN_17650_37411.700', 'AN_17650_37411.701', 'AN_17650_37411.702.txt', 'AN_17650_37411.801', 'AN_17650_37411.802.txt');
return Promise.all(fnar.map((gfname) => {
var nsalertnames = [];
console.log('Getting: ', gfname);
debugger;
pa.get(('/public_html/test/out/' + gfname), function(err, anstream){//THE PROBLEM IS THAT THIS GET GETS TRIGGERED AN EXTRA TIME FOR EVERY OTHER FILE!!!
antext = '';
console.log('Get begun for: ', gfname);
debugger;
if(err){
ferror.nsrest_trace = 'Error - could not download new AN file!';
ferror.details = err;
console.log('Error - could not download new AN file!');
console.log('************************* Exiting *************************')
logError(ferror, gfname);
}
else{
// anstream.on('data', (anchunk) => {
// console.log('Receiving data for: ', gfname);
// antext += anchunk;
// });
// anstream.on('end', () => {
// console.log('GET end for: ', gfname);
// //console.log('path to update - gfname ', gfname, '|| end text.');
// fs.appendFileSync(path.resolve('test/from', gfname), antext);
// console.log('Appended file');
// return antext;
// });//end end
};
});//get end
}));//end Promise.all and map
}).then((res99) => {
// pa.end();
// return Promise(() => {
console.log('end all. res99: ', res99);
// //res4(1);
// return 1;
// });
});
});
})();
-->> What happens here:
So I added the almost working code. What is happening is that for every other file, an additional Get request gets made (I don't know how it's being triggered), which fails with an "Unable to make data connection".
So for my iteration over this array of 6, there ends up being 9 Get requests. Element 1 gets requested (works and expected), then 2 (works and expected), then 2 again (fails and unexpected/don't know why it was triggered). Then 3 (works and expected), then 4 (works and expected), then 4 again (fails and unexpected) etc
what you need is Promise.all(), sample code for your app:
...
}).then(() => {
return Promise.all(arry.map(item => ftpclient.getFile(item)))
}).then((resultArray) => {
...
So thanks for the help (and the negative votes with no useful direction!)
I actually reached out to a good nodejs programmer and he said that there seemed to be a bug in the ftp module I was using, and even when trying to use a blackbird .map, the quick succession of requests somehow kicked off an error. I ended up using promise-ftp, blackbird, and promiseTaksQueue - the kicker was that I needed interval. Without it the ftp would end up causing a strange illogical error in the ftp module.
You need the async library. Use the async.eachSeries in situations where you need to use asynchronous operations within a loop, then execute a function when all of those are complete. There are many variations depending on the flow you want but this library does it all.
https://github.com/caolan/async
async.each(theArrayToLoop, function(item, callback) {
// Perform async operation on item here.
doSomethingAsync(item).then(function(){
callback();
})
}, function(err){
//All your async calls are finished continue along here
});

Categories

Resources