Returning promises up a function chain - javascript

Below is the structure of some code that I have written. I am attempting to load a number of files to a server using ajax and once all are complete, do some action.
function func1(items){
const results = []
for (let i=0; i<items.length; i++) {
results[i] = func2();
}
Promise.all(results).then(response => some_action())
}
function func3(params) {
return new Promise((resolve, reject) => {
//ajax call here and resolve/reject
})
}
function func2(){
if(stuff){
return func3(some_params);
} else {
return func3(other_params);
}
}
Unfortunately, it is not working as I expected. The array results is not an array of promises, but an array of undefined. I am new to Javascript promises, so any help would be appreciated.
EDIT: to respond to the comment about the possibility of a silent return, I post the actual code for func2 (lightly modified):
function func2(item, id, id2, top_level, path){
if(item.isFile){
item.file(function(file) {
if(file.name.substring(file.name.lastIndexOf('.')+1) === "docx"){
file = new File([file], file.name, {
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
})
}
if(file.name !== "desktop.ini"){
let url = 'url'
return func3(file, id, url, "", false);
}
});
} else {
window.parent.ipcRenderer.send('zip-folder', path);
window.parent.ipcRenderer.on('zipped', (event, buffer) => {
var zipped_file = new File([buffer], item.name + ".zip", {
type: "application/zip"
})
let url = "/url"
return func3(zipped_file, id, url, id2, true);
})
}
}

Your else block does not return anything:
...
else {
window.parent.ipcRenderer.send('zip-folder', path);
window.parent.ipcRenderer.on('zipped', (event, buffer) => {
var zipped_file = new File([buffer], item.name + ".zip", {
type: "application/zip"
})
let url = "/url"
return func3(zipped_file, id, url, id2, true);
})
// silently returns undefined
}
Don't be fooled by the return statement in the anonymous function. It returns the anonymous function, not func2:
window.parent.ipcRenderer.on('zipped', return this
(event, buffer) => { <----------.
var zipped_file = new File([buffer], item.name + ".zip", { |
type: "application/zip" |
}) |
let url = "/url" |
return func3(zipped_file, id, url, id2, true); -------------------'
}
)
This would be more obvious if you rewrite your code without using anonymous functions:
function func4(id, url, id2) {
return function (event, buffer) {
var zipped_file = new File([buffer], item.name + ".zip", {
type: "application/zip"
})
let url = "/url"
return func3(zipped_file, id, url, id2, true);
}
}
function func2(item, id, id2, top_level, path){
if(item.isFile){
item.file(function(file) {
if(file.name.substring(file.name.lastIndexOf('.')+1) === "docx"){
file = new File([file], file.name, {
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
})
}
if(file.name !== "desktop.ini"){
let url = 'url'
return func3(file, id, url, "", false);
}
});
} else {
window.parent.ipcRenderer.send('zip-folder', path);
window.parent.ipcRenderer.on('zipped', func4(id, url, id2))
// no return statement !!
}
}
One way to get around it is to convert it into a promise:
return new Promise((ok,fail) => {
window.parent.ipcRenderer.on('zipped', (event, buffer) => {
var zipped_file = new File([buffer], item.name + ".zip", {
type: "application/zip"
})
let url = "/url"
ok(func3(zipped_file, id, url, id2, true));
})
});
Of course, depending on how your error logic flows you may want to wrap the promise at a higher level. This merely illustrates a quick fix.

You need to return new Promise in EVERY function !

Related

How can I use async GM_xmlhttpRequest to return values in original order?

I'm trying to make a Tampermonkey script to update dates on some site.
I got an array of id's from a site, and I'm requesting data from it with the id of the array. After that, I have to return data of each Input.
As the function is async, it returns data in a random order, but I need those new arrays to return in the original order. I have tried sync and Promises, but the first is too slow and I haven't understood the second.
I can sort ids, but I also got the dates which are in the order of the first Array, so I don't know how to achieve the same order as the second id array.
Here's the code:
id = GM_getValue('id');
for (let i = 0; i < id.length; i++) {
setTimeout(() => {
console.log("Updating " + (i + 1) + " Title");
GM_xmlhttpRequest({
method: "GET",
url: "***" + id[i] + "/***",
onload: function(response) {
$(response.responseText).find("#main-form :input").each(function(x) {
if (x == 0) ids.push(parseInt($(this).val()));
if (x == 1) array.push($(this).val()));
});
}
});
}, i * 333);
}
You can use Promises to execute the GET requests in a specific order. Here's an example:
id = GM_getValue('id');
function makeGetRequest(url) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: url,
onload: function(response) {
resolve(response.responseText);
},
onerror: function(error) {
reject(error);
}
});
});
}
for (let i = 0; i < id.length; i++) {
console.log("Updating " + (i + 1) + " Title");
try {
const response = await makeGetRequest("***" + id[i] + "/***");
$(response).find("#main-form :input").each(function(x) {
if (x == 0) ids.push(parseInt($(this).val()));
if (x == 1) array.push($(this).val());
});
} catch (error) { // in case the GET request fails
console.error("Request failed with error code", error.status, ". Message is ", error.responseText);
}
}
In this example, I've created a makeGetRequest() function with returns a promise, which is resolved on GET success, but rejected on failure.
await waits for the Promise to settle before moving on and the try exists to catch Promise rejection (if the GET fails).
References:
Promise on MDN.
await on MDN.
TypeScript:
export enum HttpDataType {
JSON = "JSON"
}
import {HttpDataType} from "./enum/HttpDataType";
export default class Http {
static get(option: { url: string, dataType?: HttpDataType, synchronous?: boolean, onload?: Function }) {
option['method'] = 'GET';
if (option.synchronous) {
return new Promise((resolve, reject) => {
// #ts-ignore
GM_xmlhttpRequest({
...option,
onload: (response) => {
resolve(option.dataType === HttpDataType.JSON ? JSON.parse(response.responseText) : response.responseText);
},
onerror: (error) => {
reject(error);
}
});
})
} else {
const onload1 = function (details) {
let response;
if (option.dataType === HttpDataType.JSON) {
response = JSON.parse(details.responseText);
} else {
response = details.response;
}
option.onload(response);
}
// #ts-ignore
GM_xmlhttpRequest({...option, onload: onload1});
}
}
}
static async getXxx() {
let response = await Http.get({
url: '……',
dataType: HttpDataType.JSON,
synchronous: true
});
// #ts-ignore
if (!response || response.status !== 'success') {
console.error('getXxx error', response);
}
// #ts-ignore
return response.data;
}
this.getXxx().then((data: any) => {
// ……
});

how can use async inside .map

hi coders i have problem with this code:
const ForVideo = async data =>
data.map(story => {
return {
videoUrl: story.video.versions[0].url,
instagramId: story.pk,
videoFilename: MediaDownloader({
url: story.video.versions[0].url,
dest: "/Users/Hernan/Haip/media/" + story.account.pk + "/story/"
}),
expiresAt: story.expiringAt,
tappableObjects: HashMention(story),
influencerId: story.account.pk,
takenAt: story.takenAt,
isVideo: true,
videoDuration: story.video.duration,
displayUrl: story.imageVersions2.candidates[0].url,
imageFilename: MediaDownloader({
url: story.imageVersions2.candidates[0].url,
dest: "/Users/Hernan/Haip/media/" + story.account.pk + "/story/"
}),
callToAction: null
};
});
i call ForVideo (data) function that return a new JSON but the problem is that this dont return the item videoFilename andimageFilename (url from MediaDownloader() function)
how can i apply async / await or promise to get the full JSON and wait to MediaDownloader() function to finish ?
MediaDownloader() :
MediaDownloader: async options => {
let dir = options.dest;
try {
fs.ensureDirSync(dir);
const { filename, image } = await download.image(options);
return filename;
} catch (e) {
console.error(e);
}
}
try this.
const ForVideo = data =>
data.map(async story => {
return {
videoFilename: await MediaDownloader({
url: story.video.versions[0].url,
dest: "/Users/Hernan/Haip/media/" + story.account.pk + "/story/"
})
};
});
MediaDownloader: options => {
return new Promise((resolve, reject) => {
let dir = options.dest;
try {
fs.ensureDirSync(dir);
const { filename, image } = download.image(options);
resolve(filename);
} catch (e) {
reject(e);
}
});
}

Don't include in promise when archive is undefined

I have this code to verify if archives are undefined, but in this way I just verify if ALL the archives are undefined, I want to verify if each one is undefined.
var render_archive1 = jQuery('#archive-pratica1')[0].files[0];
var render_archive2 = jQuery('#archive-pratica2')[0].files[0];
var render_archive3 = jQuery('#archive-pratica3')[0].files[0];
var render_archive4 = jQuery('#archive-pratica4')[0].files[0];
if (render_archive1 !== undefined && render_archive2 !== undefined && render_archive3 !== undefined && render_archive4 !== undefined) {
// wait till file gets encoded
Promise.all([getBase64(render_archive1), getBase64(render_archive2), getBase64(render_archive3), getBase64(render_archive4)]).then(([data, data1, data2, data3]) => {
// push file data
form.push({'name': 'archive_pratica1', 'value': data});
form.push({'name': 'archive_pratica2', 'value': data1});
form.push({'name': 'archive_pratica3', 'value': data2});
form.push({'name': 'archive_pratica4', 'value': data3});
// send request
jQuery.ajax ({
type: "POST",
data: {
action: 'sendEditInfo',
form: form
},
url: '../wp-admin/admin-ajax.php'
})
.then((res) => {
}, err => {
});
})
}
When an archive is undefined, I can't send an ajax request because ir returns an error in the getBase64 function. So, I need to create an if to verify when each one is undefined.
function getBase64
function getBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
});
}
You could do this refactor in order to archive this task:
function readArchive(archiveId) {
var renderArchive = jQuery(`#${archiveId}`)[0].files[0];
if (renderArchive) {
return getBase64(renderArchive)
.then(data => ({ name: archiveId, data }));
}
return Promise.resolve(null);
}
Promise.all([
readArchive('archive-pratica1'),
readArchive('archive-pratica2'),
readArchive('archive-pratica3'),
readArchive('archive-pratica4'),
])
.then((archives) => {
archives
.filter(_ => _ !== null)
.forEach(_ => form.push({ 'name': _.name, 'value': _.data }));
// send request
jQuery.ajax({
type: "POST",
data: {
action: 'sendEditInfo',
form: form
},
url: '../wp-admin/admin-ajax.php'
})
})
.catch((err) => {
// do something
});
Don't reject your promises if they are undefined
function getBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = error => resolve({error:error});
});
}
then you test if the result has a error
if(!data.error)
form.push({'name': 'archive_pratica1', 'value': data});
If you want to make requests only for those not undefined try something like this:
var promises = [];
for(var i = 1; i <= 4: i++) {
renderArchive = jQuery('#archive-pratica' + i)[0].files[0];
if (renderArchive !== undefined) {
var p = getBase64(renderArchive).then((data) => {
form.push({'name': 'archive_pratica' + i, 'value': data});
});
promises.push(p);
}
}
Promise.all(promises).then(() => {
jQuery.ajax ({
type: "POST",
data: {
action: 'sendEditInfo',
form: form
},
url: '../wp-admin/admin-ajax.php'
});
});

Listing records with Airtable API

I have an Airtable base that I can retrieve records from (see code below), but I'd like to get the value for other fields besides just "Location". Using "console.log('Retrieved: ', record.get('Location'));", how do I modify this line to include in the output the field values for a field called "Size" in addition to the "Location" field? I tried "console.log('Retrieved: ', record.get('Location', 'Size'));", but that didn't work.
Here's an excerpt from my code:
// Lists 3 records in Bins
base('Bins').select({
// Selecting the first 3 records in Grid view:
maxRecords: 3,
view: "Grid view"
}).eachPage(function page(records, fetchNextPage) {
// This function (`page`) will get called for each page of records.
records.forEach(function(record) {
console.log('Retrieved: ', record.get('Location'));
});
// To fetch the next page of records, call `fetchNextPage`.
// If there are more records, `page` will get called again.
// If there are no more records, `done` will get called.
fetchNextPage();
}, function done(err) {
if (err) { console.error(err); return; }
});
OUTPUT
Retrieved 170000118
Retrieved 170000119
Retrieved 170000120
I found this repo to help in when I tried to product situations like this.
A wrapper for common functions for accessing data on an airtable.com database. All queries return promises.
Here is how it works if you want to avoid using an npm package. But ultimatly the jist of it is to either use request or some short of promise fulfillment menthod to retrive the Records.
import Airtable from 'airtable'
import _ from 'lodash'
const ENDPOINT_URL = 'https://api.airtable.com'
let API_KEY // Can only set the API key once per program
export default class AirTable {
constructor({apiKey, databaseRef}) {
if(!API_KEY) {
API_KEY = apiKey
Airtable.configure({
endpointUrl: ENDPOINT_URL,
apiKey: API_KEY
});
}
this.base = Airtable.base(databaseRef)
this.get = {
single: this.getSingleRecordFrom.bind(this),
all: this.getAllRecordsFrom.bind(this),
match: this.getAllMatchedRecordsFrom.bind(this),
select: this.getRecordsSelect.bind(this)
}
this.insert = this.createRecord.bind(this)
this.add = this.insert
this.create = this.insert
this.update = this.updateRecord.bind(this)
this.set = this.update
this.remove = this.deleteRecord.bind(this)
this.delete = this.remove
this.destroy = this.remove
this.rem = this.remove
}
async createRecord({tableName, data}) {
return new Promise((resolve, reject) => {
this.base(tableName).create(data, (err, record) => {
if (err) {
console.error(err)
reject()
return
}
console.log("Created " + record.getId())
resolve(record)
})
})
}
async updateRecord({tableName, id, data}) {
return new Promise((resolve, reject) => {
this.base(tableName).update(id, data, (err, record) => {
if (err) {
console.error(err)
reject()
return
}
console.log("Updated " + record.getId())
resolve(record)
})
})
}
async deleteRecord({tableName, id, data}) {
return new Promise((resolve, reject) => {
this.base(tableName).destroy(id, (err, record) => {
if (err) {
console.error(err)
reject()
return
}
console.log("Deleted " + record.getId())
resolve(record)
})
})
}
async getSingleRecordFrom({tableName, id}) {
console.log(tableName, id)
return new Promise((resolve, reject) => {
this.base(tableName).find(id, function(err, record) {
if (err) {
console.error(err)
reject(err)
}
resolve(record)
})
// console.log(record);
})
}
async getAllRecordsFrom(tableName) {
return this.getRecordsSelect({tableName, select: {} })
}
async getAllMatchedRecordsFrom({tableName, column, value}) {
return this.getRecordsSelect({tableName, select: {filterByFormula:`${column} = ${value}`} }) // TODO: validate input
}
async getRecordsSelect({tableName, select}) {
return new Promise((resolve, reject) => {
let out = []
this.base(tableName).select(select).eachPage((records, fetchNextPage) => {
// Flatten single entry arrays, need to remove this hacky shit.
_.map(records, r => {
_.forOwn(r.fields, (value, key) => { // If array is single
if(_.isArray(value) && value.length == 1 && key != 'rooms') {
r.fields[key] = value[0]
}
});
})
out = _.concat(out, records)
fetchNextPage();
}, (err) => {
if (err) {
console.error(err)
reject(err)
} else {
// console.log(JSON.stringify(out, null, 4))
// console.log("HI")
resolve(out)
}
})
})
}
}
Hope this Makes sense, Also trying to make an API-Proxy fetching a whole table or even use Express to fetch record id's as arrays can work as well
You can use this code line.
records.forEach(function(record) {
console.log('Retrieved: ', record.get('Location') + ' ' + record.get('Size'));
});

My arr is empty when I return it with a function

I'm trying to pass arrEntree in my return, however it's inside a promise. How would I fix this issue? The function query iniaites a new promise but like everytime ArrEntree is printed empty, because it gets called before the promise is finished processing. How would I finish the promise then only call my return...? How would I solve this issue.
function query(statement, params, first) {
return new Promise(function(resolve, reject) {
connection.query(statement, (params || []), function(err, rows) {
if (err) {
console.log("err", err);
if (err.code === "ETIMEDOUT") {
connectToDb();
return query(statement, params, first);
}
reject(err);
} else {
if (first) {
resolve(rows[0]);
} else {
resolve(rows);
}
}
});
});
}
function sendPlanEmails(){
if (plans.length === 0) {
return true;
}
const orders = plans.map(plan=>{
const arrEntree = []
const planData = srcData.plans[plan.productId];
const entrees = plan.entrees;
const entreeID = Object.keys(plan.entrees);
query('select * from entrees where entree_id in (' + entreeID.join(',') + ')')
.then(function(results){
results.forEach(function(element){
entreeID.forEach(function(second_elem){
if(element.entree_id==second_elem){
console.log('worked until here')
const quantity = plan.entrees[second_elem];
const name = element.entree;
arrEntree.push({name:name, quantity:quantity});
}
})
})
})
return {
days:plan.days.days,
planDataTitle:planData.title,
breakfast:plan.meals[0],
lunch:plan.meals[1],
dinner:plan.meals[2],
entreeArry:arrEntree
};
});
const template_mp = srcData.orderPage.mailchimp_template_mp || 'order_confirmation';
return utils.sendMealPlanEmail(template_mp, {
from: from,
order: orders,
});
}

Categories