Reputation: 6025
I tried to implement this code from Human Who Codes, along with a start method from this answer to read a CSV with creative URLs in it, download that creative from the media server, and then upload the creative Facebook's Node.js SDK. However, I am having trouble getting the Promise chain working within a Node.js file stream.
Here are the parameters to my command-line script:
Usage: creative-upload.js --inputFile --outputFile --adAccountId --uploadType --accessToken --creativeColumn --creativeIdColumn --creativeStatusColumn --maxRetries
And here is my script:
const csv = require('csv-parser');
const fs = require('fs');
const path = require('path');
const http = require('http');
const options = []; // removed yargs code, not important
// https://stackoverflow.com/a/49432604/904344
async function readStream(stream, encoding = "utf8") {
stream.setEncoding(encoding);
return new Promise((resolve, reject) => {
let data = [];
stream.on("data", chunk => data.push(chunk));
stream.on("end", () => resolve(data));
stream.on("error", error => reject(error));
});
}
// Here we wait for the myfunction to finish
// and then returns a promise that'll be waited for aswell
// It's useless to wait the myfunction to finish before to return
// we can simply returns a promise that will be resolved later
// Also point that we don't use async keyword on the function because
// we can simply returns the promise returned by myfunction
async function start() {
return await readStream(fs.createReadStream("test.csv").pipe(csv()));
}
// Call start
(async() => {
console.log('before start');
startPromise = start()
.then(data => {
for (var row of data) {
console.log(row);
const creative_url = row.creative_url;
const fileBasename = path.basename(creative_url);
const file = fs.createWriteStream("/tmp/" + fileBasename);
const request = http.get(creative_url, function(response) {
response.pipe(file);
});
// https://developers.facebook.com/docs/marketing-api/reference/ad-account/adimages/
if (uploadType == "image") {
let content = fs.readFileSync("/tmp/" + fileBasename).toString('base64');
const adimage = await account.createAdImage([], {
bytes: content
})
.then(() => {
console.log('Uploaded ' + fileBasename + " successfully.");
})
.catch((e) => {
throw e;
})
}
}
})
.catch(e => {
console.log(e);
});
console.log('after start ' + startPromise);
})();
process.exit(1);
And this is the error I get:
creative-upload.js:109
const adimage = await account.createAdImage([], {
^^^^^
SyntaxError: await is only valid in async function
at new Script (vm.js:80:7)
at createScript (vm.js:274:10)
at Object.runInThisContext (vm.js:326:10)
at Module._compile (internal/modules/cjs/loader.js:664:28)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:712:10)
at Module.load (internal/modules/cjs/loader.js:600:32)
at tryModuleLoad (internal/modules/cjs/loader.js:539:12)
at Function.Module._load (internal/modules/cjs/loader.js:531:3)
at Function.Module.runMain (internal/modules/cjs/loader.js:754:12)
at startup (internal/bootstrap/node.js:283:19)
I got rid of the async/await and this is my new code:
var promises = [];
fs.createReadStream(inputFile)
.pipe(csv())
.on('data', (row) => {
console.log(row);
const creative_url = row['Banner URL'];
console.log('Creative URL: ' + creative_url);
const fileBasename = path.basename(creative_url);
console.log('File Basename: ' + fileBasename);
const file = fs.createWriteStream("/tmp/" + fileBasename);
const request = http.get(creative_url, function(response) {
response.pipe(file);
});
console.log('Request: ' + request);
// https://developers.facebook.com/docs/marketing-api/reference/ad-account/adimages/
if (uploadType == "image") {
let content = fs.readFileSync("/tmp/" + fileBasename);
if (content == null || content.toString('base64') == '') {
console.log('ERROR! Could not get base64 content of tmp file.');
return;
}
/*{encoding: 'utf8'}, function(err, data) {
if (err) {
console.log('Error: ' + error);
return null;
} else {
return data.toString('base64');
}
});*/
content = content.toString('base64');
promises.push(account.createAdImage([], {
bytes: content
})
.then(() => {
console.log('Uploaded ' + fileBasename + " successfully.");
})
.catch((e) => {
console.log(e);
}));
}
// https://developers.facebook.com/docs/marketing-api/reference/ad-account/advideos/
else if (uploadType == "video") {
let content = fs.readFileSync("/tmp/" + fileBasename).toString('base64');
if (content == null || content.toString('base64') == '') {
console.log('ERROR! Could not get base64 content of tmp file.');
return;
}
content = content.toString('base64');
promises.push(account.createAdVideo([], {
bytes: content
})
.then(() => {
console.log('Uploaded ' + fileBasename + " successfully.");
})
.catch((e) => {
console.log(e);
}));
}
})
.on('end', () => {
Promise.all(promises);
console.log('CSV file successfully processed');
});
This code successfully uploads the last two creative in my CSV. However, all of the other rows fail to maintain the content variable, which is empty by the time it gets to the Facebook code. Here is my output:
{ 'Banner Name': '...',
'Banner Size': '728x90',
'Banner URL':
'http://s3.amazonaws.com/beta-adv-cdn/jpeg_ads/c62cf4b9-4d86-4613-be15-b6c3b58babba.jpeg' }
Creative URL: http://s3.amazonaws.com/beta-adv-cdn/jpeg_ads/c62cf4b9-4d86-4613-be15-b6c3b58babba.jpeg
File Basename: c62cf4b9-4d86-4613-be15-b6c3b58babba.jpeg
Request: [object Object]
ERROR! Could not get base64 content of tmp file.
{ 'Banner Name': '...,
'Banner Size': '728x90',
'Banner URL':
'http://s3.amazonaws.com/beta-adv-cdn/jpeg_ads/95714da4-0085-4c0c-ba73-0346197c91db.jpeg' }
Creative URL: http://s3.amazonaws.com/beta-adv-cdn/jpeg_ads/95714da4-0085-4c0c-ba73-0346197c91db.jpeg
File Basename: 95714da4-0085-4c0c-ba73-0346197c91db.jpeg
Request: [object Object]
ERROR! Could not get base64 content of tmp file.
CSV file successfully processed
200 POST https://graph.facebook.com/v10.0/...
Uploaded 58748e44-83c7-4283-b090-36ce8dd8070b.jpeg successfully.
200 POST https://graph.facebook.com/v10.0/...
Uploaded dc5e9dcc-c6ab-4cbe-a334-814b4af4c4fa.jpeg successfully.
There doesn't seem to be much documentation on the Facebook Node.JS SDK and even less questions about it on Stack Exchange, so any help would be appreciated.
Upvotes: 1
Views: 435
Reputation: 5190
I'm not exactly sure what you're expecting here. First, console.log('after start ' + startPromise);
is dead code; it's after a return statement. Secondly, you're kicking off an async anonymous function without awaiting it, then call process.exit(1);
.
You should wait for the Promise created by the self-invoking function to resolve and handle rejections, too. Something along the lines:
console.log('before start');
const startPromise = start()
.then(data => {
for (var row of data) {
console.log(row);
const creative_url = row.creative_url;
const fileBasename = path.basename(creative_url);
const file = fs.createWriteStream("/tmp/" + fileBasename);
const request = http.get(creative_url, function(response) {
response.pipe(file);
});
// https://developers.facebook.com/docs/marketing-api/reference/ad-account/adimages/
if (uploadType == "image") {
let content = fs.readFileSync("/tmp/" + fileBasename).toString('base64');
// Upload image
}
}
})
.catch(e => {
console.log(e);
});
Edit: as pointed out by @Tomalak in the comments, the async wrapper function is not needed at all.
Upvotes: 4