I have made an Azure function with 3 calls to db. But as they work async only the first item gets added.
My code below. So to be exact Context.Done but first, the calls have to be finished before going to the next call hope someone can help me.
It seems it has to do with the Azure function context as promises did not work either.
const rp = require('request-promise');
const azure = require("azure-storage");
var feed = require('feed-read');
const env = require('dotenv').config();
const con = "storageconnection";
function guid() {
return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
s4() + '-' + s4() + s4() + s4();
}
function CreateEntitie(item) {
var link = item.link;
var team = link.substr(39, link.length);
team = team.substr(0, team.indexOf('/'));
var newlink = link.substr(0, link.lastIndexOf('#'));
var pubdate = item.published;
var titel = item.title;
var Entity = {
PartitionKey: { '_': '' },
RowKey: { '_': '0' },
Gelezen: { "_": false },
Team: { "_": team },
Titel: { "_": item.title },
Com_url: { "_": newlink },
pubdate: { "_": item.published, '$': 'Edm.DateTime' }
}
return Entity;
}
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
function getrownummer(azure, tableSvc, entitie) {
for (var x = 0; x < entitie.length; x++) {
var query = new azure.TableQuery()
.where('com_url eq ?', entitie[x].Com_url._);
//check if exists and update
tableSvc.queryEntities('Posts', query, null, function (error, posts, response) {
if (!error) {
//update
var rownummer = "0";
for (var a = 0; a < posts.length; a++) {
rownummer = posts[a].RowKey._;
}
if (rownummer === "0") {
rownummer = guid();
}
entitie[x].RowKey._ = rownummer;
}
});
}
return entitie;
}
function InsertorReplaceItem(tableSvc, entitie) {
var returnvalue;
tableSvc.insertOrReplaceEntity('posts', entitie, function (error, inputed, response) {
if (!error) {
console.log(entitie.Com_url._ + "rownummer=" + entitie.RowKey._);
// Entity updated
return true;
}
else {
return false;
}
});
}
module.exports = function (context, myTimer) {
var timeStamp = new Date().toISOString();
if (myTimer.isPastDue) {
context.log('JavaScript is running late');
}
tableSvc = azure.createTableService(con);
tableSvc.queryEntities('rssfeeds', null, null, function (error, result, response) {
if (error) {
context.log('well that didn\'t work: ' + err.stack);
context.done();
} else {
context.log(result)
};
if (result) {
var i = 1;
var lastupdates = []
feed(result.entries[i].rssurl._, function (err, articles) {
if (err) throw err;
var newdate = new Date(articles[0].published).getTime();
for (var x = 0; x < articles.length; x++) {
//console.log(articles[x].title)
var from = new Date(articles[x].published).getTime();
var to = new Date(result.entries[i].Lastupdate._).getTime();
if (from >= to) {
var entitie = null;
var entitie = CreateEntitie(articles[x]);
lastupdates.push(entitie);
// context.log(entitie.titel);
}
if (newdate <= from) {
newdate = from;
}
}
context.log(lastupdates);
var completeArticle = [];
completeArticle = getrownummer(azure, tableSvc, lastupdates);
context.log(completeArticle);
if (completeArticle) {
for (var x = 0; x < completeArticle.length; x++) {
var endresult = InsertorReplaceItem(tableSvc, completeArticle[x]);
// context.log(endresult);
}
if (endresult) {
context.done;
}
}
//update date to latest date
});
// }
}
});
}
Related
I have a problem when using await in a for loop. Every time it hits the await funtion it executes it fine, but it stops loping through the rest of the array that is looping through. I'm using nodejs with axios to send http request to a restAPI. The function that is in the api is big. So I thought maby that is the problem but it wasn't. Also the api uses its own await functions to, but that wasn't the problem either(As far as I know).
Here is my code for the request
var files = await globPromise("assets/series/**/*.json");
var json = null;
var file = null;
var series = [];
for (i = 0; i < files.length; i++) {
file = files[i];
var raw = fs.readFileSync(file);
json = JSON.parse(raw);
if (json.type === "series") {
try {
var userId = null;
if (req.user == null) {
userId = req.query.userid;
} else {
userId = req.user.id;
}
const response = await axios.get("http://" + host + "/series/info/" + json.id + "?userid=" + userId);
series.push(JSON.parse(response.data));
} catch (error) {
console.log(error);
series.push(json);
}
}
}
res.json(JSON.stringify(series));
});
And also the api side:
app.get('/series/info/:id', async(req, res) => {
var files = await globPromise("assets/series/**/*.json");
var json = null;
var file = null;
for (i = 0; i < files.length; i++) {
file = files[i];
var raw = fs.readFileSync(file);
json = JSON.parse(raw);
if (json.type === "series" && json.id === req.params.id) {
break;
}
json = null;
}
let path = pathFs.dirname(file) + "/";
try {
var seriesFiles = await fsPromise.readdir(path);
var latestWatchedVideo = null;
var latestWatchedTime = null;
var latestWatchTime = null;
var latestWatchDuration = null;
var seasonCount = 0;
var seasons = [];
for (i = 0; i < seriesFiles.length; i++) {
seriesFile = seriesFiles[i];
if (fs.lstatSync(path + "/" + seriesFile).isDirectory()) {
if (!seriesFile.startsWith("s")) {
continue;
}
seasonCount++;
try {
var videoFiles = await fsPromise.readdir(path + "/" + seriesFile + "/");
var videos = [];
for (let i = 0; i < videoFiles.length; i++) {
const video = videoFiles[i];
if (video.endsWith(".json")) {
var rawVideo = fs.readFileSync(path + "/" + seriesFile + "/" + video);
videoJson = JSON.parse(rawVideo);
const query = util.promisify(con.query).bind(con);
var userId = null;
if (req.user == null) {
userId = req.query.userid;
} else {
userId = req.user.id;
}
var results = await query(`SELECT * FROM watched WHERE video_id = "${videoJson.id}" AND user_id = "${userId}"`);
if (results.length > 0) {
var updated = JSON.parse(JSON.stringify(results[0].updated));
var duration = JSON.parse(JSON.stringify(results[0].duration));
var time = JSON.parse(JSON.stringify(results[0].time));
if (latestWatchedVideo == null) {
latestWatchedVideo = videoJson.id;
latestWatchedTime = updated;
latestWatchTime = time;
latestWatchDuration = duration;
} else {
if (latestWatchedTime < updated) {
latestWatchedVideo = videoJson.id;
latestWatchedTime = updated;
latestWatchTime = time;
latestWatchDuration = duration;
}
}
}
videos.push(videoJson);
}
}
function compare(a, b) {
if (a.episode < b.episode) {
return -1;
}
if (a.episode > b.episode) {
return 1;
}
return 0;
}
videos.sort(compare);
seasons.push({
season: seasonCount,
title: seriesFile.replace("s" + seasonCount, ""),
videos: videos
});
} catch (error) {
console.log(error);
}
}
}
json.seasonCount = seasonCount;
json.seasons = seasons;
json.latestWatchDuration = latestWatchDuration;
json.latestWatchTime = latestWatchTime;
json.latestWatchedVideo = latestWatchedVideo;
json.latestWatchedTime = latestWatchedTime;
res.json(JSON.stringify(json));
} catch (error) {
console.log(error);
}
});
Is there something (important) about await and async that I've missed?
Edit: my problem is that is loops fine through the first item and the await is working fine too, but it stops the loop and executes the next lines of code like there are no other items in my array.
Solved: I tried using the for/of and it works now. I don't know whats is so different between de default for and this one but it works!
I am still pretty new to this, so forgive me if I dont' say this correctly. We have an array.reduce that calls a method with a returning promise that iterates through a list of files and post results to the db. Everything was working great, until it ran into a field that had an apostrophe in it and then the db insert fails. This is the field value. 'Expected 100002822' to be 100002822.'
I tried adding a replaceAll on the field and now get an error in the array.reduce.
Here is the .reduce
console.log('Found test results in ' + matches.length + ' files. Parsing and posting to the database now...');
var startTime = moment();
var parser = new Parser();
matches.reduce(function (p, val) {
return p.then(function () {
return parser.parseResults(val);
});
}, Promise.resolve()).then(function (finalResult) {
var endTime = moment();
var testDuration = moment.duration(endTime.diff(startTime));
console.log(chalk.blue('*** File parsing time: ' + testDuration.humanize() + ' ***'));
if (finalResult.insertSuccess == matches.length) {
var publishOut = {
totalFiles: matches.length,
totalTests: 0,
totalTestsSuccess: 0,
totalTestsFailed: 0
}
publishOut.totalTests += finalResult.totalTests;
publishOut.totalTestsSuccess += finalResult.testPassedCount;
publishOut.totalTestsFailed += finalResult.testFailedCount;
console.log(`Successfully inserted ${finalResult.insertSuccess} of ${publishOut.totalTests} test results.`);
// for (var i = 0; i < matches.length; i++) {
// var currentFile = `./testing/results/${matches[i]}`;
// fs.unlinkSync(currentFile);
// }
resolve(publishOut);
} else {
reject('Only ' + finalResult.insertSuccess + ' of ' + matches.length + ' successfully posted to the database');
}
}, function (err) {
reject('error in reduce', err);
});
I have tried several different ways of using the replaceAll with the same failure. It hits this code from the array.reduce
}, function (err) {
reject('error in reduce', err);
});
And this is the called method. The added code causing the failure in the .reduce is this Message = expectation.message.replaceAll("'", "");
protractorParser.prototype.parseResults = function (fileName) {
return new Promise((resolve, reject) => {
//console.log('In parseresults', fileName);
var currentFile = './testing/results/' + fileName
json.readFile(currentFile, function (err, obj) {
if (err != null) {
console.log('error reading file', err);
reject(err);
} else {
resolve(obj);
}
});
}).then(function (obj) {
var results = [];
for (var suite in obj) {
var specs = obj[suite].specs;
for (let i = 0; i < specs.length; i++) {
const assert = specs[i];
const tcR = /TC[\d]+/;
const tc = assert.description.match(tcR);
let Passed = 1;
let Message = '';
let Stack = '';
testResults.totalTests++;
if (assert.failedExpectations.length) {
const expectation = assert.failedExpectations[assert.failedExpectations.length - 1];
Passed = 0;
Message = expectation.message.replaceAll("'", "");
Stack = expectation.stack.split('\n')[1].trim();
testResults.testFailedCount++
} else {
testResults.testPassedCount++
}
if (tc != null) {
const time = moment().utcOffset(config.get('settings.timeOffset')).format('YYYY-MM-DDTHH:mm:ss');
const promise = utility.TestDataManager.insertAutomationResults(tc[0], assert.description, Passed, process.env.testBuild, 'P', Message, Stack, 0, time, '');
results.push(promise.then(() => {
//fs.unlinkSync(currentFile);
testResults.insertSuccess++;
//console.log('insertSuccess', testResults.insertSuccess);
},
err => { console.log('… failed', err); throw err; }
));
} else {
console.log('no test case found for test: ' + assert.description + ' -- skipping');
// I don't think you want to `throw err` here, right?
}
}
}
return Promise.all(results).then(() => testResults);
});
};
I'm quite new to the topic and i'm still having some issues with my mailparser. Though searching and finding emails in the email header (mail.from) does work, it doesn't work in the email body. Does anybody have some experience with that and is willing to help? You can find the function i'm talking about under the "// Check for other addresses in Mail-Body (Doesn't work yet)"-comment. I think, that my Regex is correct. Also if the matchAll-Function give back an array and it can't be saved in the the subscriber.email-object, it shall be at least logged to the console. Also i checked manually in the inbox if there are mails with email adresses in the mail body. There are at least two, which shall be found..
The part of the App.js, that does the mailparsing:
const simpleParser = require('mailparser').simpleParser;
//const htmlparser = require("htmlparser2");
var fs = require('fs');
var config = require('./config');
var Imap = require('imap');
var imap = new Imap(config.imap);
var blacklistString = '';
String.prototype.matchAll = function(regexp) {
var matches = [];
this.replace(regexp, function() {
var arr = ([]).slice.call(arguments, 0);
var extras = arr.splice(-2);
arr.index = extras[0];
arr.input = extras[1];
matches.push(arr);
});
return matches.length ? matches : null;
};
function openInbox(subbox,cb) {
imap.openBox('INBOX.'+subbox, true, cb);
}
function getBoxes(cb) {
imap.getBoxes(cb);
}
function showBoxes(boxes) {
imap.end();
}
function logArrayElements(element) {
if(element[1].indexOf('placeholder.de')==-1){
addToBlacklistString(element[1]);
}
}
function addToBlacklistString(str) {
blacklistString += str+"\n";
}
function writeBlacklistFile() {
fs.appendFile('data/data.csv', blacklistString, function (err) {
if (err) throw err;
console.log('Saved!');
});
}
function search(searchArray, regex){
imap.search(searchArray, function(err, results) {
if (err) throw err;
var temp = 0;
var mailtemp = [];
var f = imap.fetch(results, { bodies: '' });
f.on('message', function(msg, seqno) {
console.log('Message #%d', seqno);
var prefix = '(#' + seqno + ') ';
msg.on('body', function(stream, info) {
simpleParser(stream, (err, mail)=>{
//console.log(temp);
//console.log(mail.subject);
/*fs.writeFile('data/'+seqno+'.txt',mail.text, function(err){
console.log(err);
});*/
//var text = mail.text;
// New Subscriber Object
var subscr = new Subscriber({nr: '', mailIdent: '', from: '', emails: '', text:'', uLink: '', anwalt: false });
subscr.nr = seqno;
//Check for From-Address
if(!!mail.from) {
//console.log(mail.from.value);
for(var i = 0; i < mail.from.value.length; i++) {
mailtemp = mail.from.value[i].address.matchAll(regex);
mailtemp.forEach(function(element){
/*fs.appendFile('data/data.csv', element[0] + "\n", function(error){
console.log(error);
});*/
subscr.from = element[0];
});
if(!!mailtemp) {
mailtemp.forEach(logArrayElements);
}
}
}else{
//console.log(mail.text);
}
// Message-ID
if(!!mail.messageId) {
subscr.mailIdent = mail.messageId;
}
console.log(mail.messageId);
// Check for other addresses in Mail-Body (Doesn't work yet)
var regexEmails = new RegExp('/([\w\.\-\_\#\+]+#[\w\.\-\_äüö]+\.[a-zA-Z]+)/g');
if(!!mail.text){
if(mail.text.matchAll(regexEmails)!=null) {
subscr.emails = mail.text.matchAll(regexEmails);
console.log(subscr.emails);
}
}
/* Split mail.text at substrings in substr-array. Extend if necessary..
*
* Also check for 'Anwalt'-Expression in splitted Substring
*
* If mail.text doesn't exist -> Check for html body and convert it to text-format
*/
//var regexLink = new RegExp('\.de\/(unsubscribe|austragen)\/([^\"]+)');
var regexAnwalt = new RegExp('nwalt|echtsanwalt|rechtlicher');
if(!!mail.text) {
var substr = ["schrieb pplaceholder.de", "Von: \"placeholder.de", "Von: pplaceholder.de", "From: placeholder.de", "Ursprüngliche Nachricht"];
for (var i = 0; i<substr.length; i++) {
if(mail.text.indexOf(substr[i]) > -1) {
var textTemp = mail.text;
var arr = textTemp.split(substr[i]);
if(arr[0].matchAll(regexAnwalt)!=null) {
subscr.anwalt = true;
};
subscr.text = arr[0];
break;
} else {
subscr.text = mail.text;
}
}
//console.log(arr);
}
else
{
var html = mail.html;
var text = htmlToText.fromString(html, {
noLinkBrackets: true,
ignoreImage: true,
uppercaseHeadings: false,
preserveNewlines: false,
wordwrap:130,
format: {
heading: function (node, fn, options) {
var h = fn(node.children, options);
return '\n==== ' + h + ' ====\n\n';
}
}
});
subscr.text = text;
}
mail.headers.forEach(function(value, key) {
//console.log(value);
});
subscr.save();
//console.log(subscr);
temp++;
});
});
msg.once('end', function() {
console.log(prefix + 'Finished');
});
});
f.once('error', function(err) {
console.log('Fetch error: ' + err);
});
f.once('end', function() {
console.log('Done fetching all messages!');
//writeBlacklistFile();
imap.end();
});
});
}
imap.once('ready', function() {
openInbox('Test',function(err, box) {
var searchArray = [['FROM', '#']];
search(searchArray,/([\w\.\-\_\#\+]+#[\w\.\-\_äüö]+\.[a-zA-Z]+)/g);
});
});
imap.once('error', function(err) {
console.log(err);
});
imap.once('end', function() {
console.log('Connection ended');
});
imap.connect();
app.listen(2700, function(){
console.log("Listening on Port 2700")
});
module.exports = app;
subscriber.js
const mongoose = require('mongoose');
var subscriberSchema = mongoose.Schema({
nr: Number,
mailIdent: String,
from: String,
emails: String,
text: String,
uLink: String,
anwalt: Boolean
});
var Subscriber = module.exports = mongoose.model('Subscriber', subscriberSchema);
//get Subscriber
module.exports.getSubscribers = function(callback, limit){
Subscriber.find(callback).limit(limit);
};
module.exports.getSubscriberByID = function(_id, callback){
Subscriber.findById(_id, callback);
};
The Regex for the Emails was a little bit wrong.
Also i didn't noticed that the matchAll-Fct. is giving back a two-dimensional Array. Here is the changed part of the code:
var regexEmails = new RegExp("([\\w\\.\\-\\_\\#\\+]+#[\\w\\.\\-\\_äüö]+\\.[a-zA-Z]+)");
var temp1 = mail.text.matchAll(regexEmails);
if(!!temp1){
//console.log(temp1);
for(var i =0; i<temp1.length; i++) {
if(temp1[0][i]!=='info#service.placeholder.de' && temp1[0][i] !== "info#placeholder.de"){
subscr.emails += temp1[0][i];
}
}
}
I looked at various answers but I can't not find a way to get this going for myself.
I have a function (in node.js) that selects a winner out of a pool, when it selects the winner though, I need it to send a event to the client where it runs a function with data. The data would be the array index of the winner.
Selecting a winner:
var endRound = function() {
ref.child('currentJackpot').once('value', function(data) {
var currentJackpot = data.val();
var winnerArray = [];
var winnerObj = {};
winnerObj.items = [];
for (var i = 0; i < currentJackpot.players.length; i++) {
winnerObj.items = winnerObj.items.concat(currentJackpot.players[i].items);
var playerValue = currentJackpot.players[i].itemsValue * 100;
currentJackpot.players[i].chance = ((currentJackpot.players[i].itemsValue / currentJackpot.jackpotValue) * 100).toFixed(2);
for (var j = 0; j < playerValue; j++) {
winnerArray.push(i);
}
}
var formatted = currentJackpot.roundHash.replace(/[.#$/]/g, "");
sgRef.child(formatted).once('value', function(data) {
var sgData = data.val();
salt = sgData.salt;
rngStr = sgData.rngStr;
console.log('ROUND ENDED! hash: ', hash, ' salt: ', salt, ' rngStr: ', rngStr);
currentJackpot.tickets = currentJackpot.jackpotValue * 100;
currentJackpot.winningTicket = Math.floor((parseFloat(rngStr, 2) * currentJackpot.tickets));
currentJackpot.winningNumber = (parseFloat(rngStr, 2) * 100).toFixed(2) + "%";
currentJackpot.winner = currentJackpot.players[winnerArray[currentJackpot.winningTicket]];
currentJackpot.salt = salt;
currentJackpot.rngStr = rngStr;
winnerObj.jackpotValue = currentJackpot.jackpotValue;
currentJackpot.jackpotValue = currentJackpot.jackpotValue.toFixed(2);
winnerObj.winner = currentJackpot.winner;
winnerObj.tradeToken = currentJackpot.winner.tradeToken;
ref.child('endedJackpots').push(currentJackpot);
bcrypt.genSalt(10, function(err, data) {
salt = data;
rngStr = JSON.stringify(rng());
bcrypt.hash(rngStr, salt, function(err, data) {
hash = data;
ref.child('currentJackpot').set({
itemsCount: 0,
jackpotValue: 0,
roundHash: hash,
}, function() {
console.log('NEW ROUND! hash: ', hash, 'salt: ', salt, 'rngStr: ', rngStr);
var formatted = hash.replace(/[.#$/]/g, "");
var sgJackpotRef = sgRef.child(formatted);
sgRef.set({}, function() {
sgJackpotRef.set({
salt: salt,
rngStr: rngStr,
}, function() {
request.post({
url: '*******',
body: winnerObj,
json: true,
}, function(error, response, body) {
if (error) {
console.log(error);
setPollTimer(10000);
} else {
usersRef.child(winnerObj.winner.id).once('value', function(data) {
var userData = data.val();
if (data.child('won').exists()) {
userData.won = (Math.floor(parseFloat(userData.won, 2)) + Math.floor(parseFloat(winnerObj.jackpotValue, 2))).toFixed(2);
} else {
userData.won = (Math.floor(parseFloat(winnerObj.jackpotValue, 2))).toFixed(2);
}
usersRef.child(winnerObj.winner.id).update({
won: userData.won
}, function() {
console.log('Added winnings to user data');
});
});
console.log('Making a withdraw request now to bot');
setPollTimer(10000);
}
});
});
});
});
});
});
});
});
};
And the function it should run client side:
function slotMachine(winnerIndex) {
var params = {
active: 3,
randomize: function(activeElementIndex){
return 1;
}
};
var machine = $('.slot').slotMachine( params );
$("#slotMachineButton").click(function(){
machine.shuffle(3, function(){
$(this).text("Index: " + this.active);
});
});
}
I'm completely stuck and stressed though, this isn't my code and It's a lot of code I can't grasp where to begin to do this. Any help?
I've tried this a thousand different ways in a thousand different times and my JS code won't come out the way I want it. When I run it in the Mozilla scratchpad, I get "userHand is undefined" and the second printHand shows as undefined, too. Could someone show me where are the errors in my blackjack game?
function Card (s, n) {
var suit = s;
var number = n;
this.getNumber = function () {
return number;
};
this.getSuit = function () {
return suit;
};
this.getValue = function () {
if (number > 10) {
return 10;
} else if (number === 1) {
return 11;
} else {
return number;
}
};
}
var cardNames = {1:"Ace", 2:"2", 3:"3", 4:"4", 5:"5", 6:"6", 7:"7", 8:"8", 9:"9", 10:"10", 11:"Joker", 12:"Queen", 13:"King"};
var suitNames = {1:"Clubs", 2:"Diamonds", 3:"Hearts", 4:"Spades"};
var deal = function () {
var s = Math.floor(Math.random() * 4 + 1);
var n = Math.floor(Math.random() * 13 + 1);
return new Card(s, n);
};
function Hand(){
var cards = [];
cards.push(deal());
cards.push(deal());
this.getHand = function () {
return cards;
};
this.score = function () {
var score;
for (i = 0; i < cards.length; i++) {
score = score + cards[i].getValue();
}
for (i = 0; i < cards.length; i++) {
if (score > 21 && cards[i].getValue() === 11) {
score = score - 10;
}
} return score;
};
this.printHand = function () {
for (i = 0; i < cards.length; i++) {
var hand;
if (i === 0) {
hand = cardNames[cards[i].getNumber()] + " of " + suitNames[cards[i].getSuit()];
} else {
hand = hand + " and a " + cardNames[cards[i].getNumber()] + " of " + suitNames[cards[i].getSuit()];
}
} alert(hand);
};
this.hitMe = function () {
cards.push(deal());
};
}
var playAsDealer = function () {
var playDealer = new Hand();
while (playDealer.score() < 17) {
playDealer.hitMe();
}
this.printHand = function () {
return playDealer.printHand();
};
this.score = function () {
return playDealer.score();
};
};
var playAsUser = function () {
var playUser = new Hand();
this.printHand = function () {
return playUser.printHand();
};
this.score = function () {
return playUser.score();
};
var decision = confirm("Your hand is " + playUser.printHand() + ". Click OK to hit or Cancel to stand");
for (i = 0; decision !== false; i++) {
playUser.hitMe();
decision = confirm("Your hand is " + playUser.printHand() + ". Click OK to hit or Cancel to stand");
}
};
var declareWinner = function (userHand, dealerHand) {
if ((userHand.score < dealerHand.score) || userHand.score > 21) {
return "You lose.";
} else if (userHand.score > dealerHand.score) {
return "You win.";
} else {
return "You tied.";
}
};
var playGame = function () {
var user = playAsUser();
var dealer = playAsDealer();
declareWinner(user, dealer);
console.log("User got " + user.printHand());
console.log("Dealer got " + dealer.printHand());
};
playGame();
You aren't returning nothing on printHand()
I just added the return statement and worked. See this fiddle
this.printHand = function () {
for (i = 0; i < cards.length; i++) {
var hand;
if (i === 0) {
hand = cardNames[cards[i].getNumber()] + " of " + suitNames[cards[i].getSuit()];
} else {
hand = hand + " and a " + cardNames[cards[i].getNumber()] + " of " + suitNames[cards[i].getSuit()];
}
}
//alert(hand); //remove this alert
return hand; // <----- solution
};