Making function waiting for args to resolve - asynchronous

Is there anything out there already that do similar to the following.
/**
* ex.
* const ret = await asyncify(Math.max)(arg1, arg2);
*/
asyncify(func) {
return async (...inputs) => {
// wait for all inputs being resolved
const inputsResolved = await Promise.all(inputs);
return await func(...inputsResolved);
};
}
/**
* ex.
* const ret = await execute(Math.max, arg1, arg2);
*/
async execute(func, ...inputs) {
// wait for all inputs being resolved
const inputsResolved = await Promise.all(inputs);
return await func(...inputsResolved);
}
With these functions (one or the other) I can then do something asynchronous without complex code structure to make sure the tasks are executed in correct sequence and maximally in parallel.
// Task A and B can run in parallel
const retA = asyncTaskA(payloadA);
const retB = asyncTaskB(payloadB);
// Task C and D depend on A and B, but they can run in parallel as well.
const retC = asyncify(
(payloadC1, payloadC2) => {
asyncTaskC(payloadC1.someField, payloadC2.otherField);
}
)(retA, retB);
const retD = asyncify(asyncTaskD)(retA, retB);
// Task A and B can run in parallel
const retA = asyncTaskA(payloadA);
const retB = asyncTaskB(payloadB);
// Task C and D depend on A and B, but they can run in parallel as well.
const retC = execute(
(payloadC1, payloadC2) => {
asyncTaskC(payloadC1.someField, payloadC2.otherField);
},
retA,
retB
);
const retD = execute(asyncTaskD, retA, retB);
If not, is it something that worth adding to the Promise similar to Promise.all?
const retC = Promise.asyncify(asyncTaskD)(retA, retB));
Update: adding a more complex example:
/**
* A -> B
* B -> C
* A -> D
* B,D -> E
* C,E -> F
*/
async function aComplexWorkflow() {
const A = Lib.execute(asyncTaskA);
const B = Lib.execute(asyncTaskB, A);
const C = Lib.execute(asyncTaskC, B);
const D = Lib.execute(asyncTaskD, A);
const E = Lib.execute(asyncTaskE, B, D);
const F = Lib.execute(asyncTaskF, C, E);
return F;
}

Is there anything out there already that do similar to the following.
No.
I can then do something asynchronous without complex code structure
I don't think your code is any less complex than just writing
const [retA, retB] = await Promise.all([
asyncTaskA(payloadA),
asyncTaskB(payloadB),
]);
const retC = asyncTaskC(retA, retB);
const retD = asyncTaskD(id, forecast.result);
I'd even argue that using execute/asyncify makes it harder to understand than just inlining those.
Is it something that is worth adding to the Promise similar to Promise.all?
No - and please don't modify builtins!

Related

I don't know how to wait for the execution of complex function using await Promise.all to complete

const fetchPrice = async (symbols) => {
const prices = {};
await Promise.all(
symbols.map(async (symbol) => {
const priceInformation =
(base.id, await base.fetch_ticker((Symbol = replacedSymbol)));
prices[symbol] = priceInformation;
})
);
return prices;
};
const PriceObj = await fetchPrice(SYMBOL_LIST);
console.log(PriceObj);
In the above code, I don't know how to wait for fetchPrice to be processed before executing console.log();.
The console.log(); only returns unexpected values, such as undefined or Promise.
(If I put console.log(prices); before return at the end of the fetchPrice function, then only this console.log(); returns the expected value, so the fetchPrice function itself is working).
I saw some information somewhere that return new Promise should be used, but it was too complicated for a beginner to understand.
How can I wait for fetchPrice to process and then display the last console.log(PriceObj); value correctly?
Here's a runnable version of your code where I've simulated fetch_ticker() as an asynchronous function that returns a promise that resolves in a random amount of time and modified some things in your code that seemed odd or incorrect.
function randInt(min, max) {
const r = Math.random();
return Math.floor((r * (max - min)) + min);
}
function delay(t, v) {
return new Promise(resolve => setTimeout(resolve, t, v));
}
// simulate base.fetch_ticker
// resolves in a random amount of time with a random integer value
const base = {
fetch_ticker() {
let t = randInt(100, 1000);
return delay(t, t);
}
}
const fetchPrice = async (symbols) => {
const prices = {};
await Promise.all(symbols.map(async (symbol) => {
const priceInformation = await base.fetch_ticker(symbol);
prices[symbol] = priceInformation;
}));
return prices;
};
async function run() {
const SYMBOL_LIST = ["IBM", "GM", "TSLA", "GOOG"];
const PriceObj = await fetchPrice(SYMBOL_LIST);
console.log(PriceObj);
}
run().then(result => {
console.log("done");
}).catch(err => {
console.log(err);
});
As you can see in my comments to your question, there were parts of your code that didn't make sense to me. It is unclear what you're trying to do with the (Symbol = replacedSymbol) part of this since Symbol is a built-in global and replacedSymbol is not shown in your code at all and it's unclear why you would be doing this assignment in the middle of passing arguments:
base.fetch_ticker((Symbol = replacedSymbol))
And, it's unclear why the base.id is involved in this:
const priceInformation =
(base.id, await base.fetch_ticker((Symbol = replacedSymbol)));
This statement:
(base.id, await base.fetch_ticker((Symbol = replacedSymbol)))
will just evaluate to the the value of the second item in the comma list and the base.id will have no effect at all.
In my code example above, I've removed both of those elements since there's no explanation or reasoning provided for them.

Firebase stub transaction

I am trying to stub a transaction over real time database table.The transaction is inside a function called by a trigger (non HTTP). I can fire the trigger, but I'm not able to stub transaction like this:
var codeRef = admin.database().ref('last_code')
return codeRef.transaction(function (currentCode) {
return currentCode + 1
})
.then(result => {
const {error, committed, snapshot} = result
return snapshot.val()
})
I am using stub Sinon with mocha Unit testing of Cloud Functions. This is the way I tried:
const test = require('firebase-functions-test')();
adminInitStub = sinon.stub(admin, 'initializeApp');
// Now we can require index.js and save the exports inside a namespace called myFunctions.
myFunctions = require('../index');
const refParam = 'last_code';
const databaseStub = sinon.stub();
const refStub = sinon.stub();
const transactionStub = sinon.stub();
Object.defineProperty(admin, 'database', { get: () => databaseStub });
databaseStub.returns({ ref: refStub });
refStub.withArgs(refParam).returns({transaction: function(code) => ({committed: true, snapshot: 999});
But stub transaction fails. I am sure that last line is incorrect, but don't see the solution.
Finally I resolve in this way:
const obj = {
a: (() => function(code){
return 999
})};
let dbSnap = {
val: function() {
return 999;
}
};
var resolveStub = sinon.stub(obj,"a");
const result = {"error": null, "committed" : true, "snapshot" : dbSnap};
resolveStub.resolves(result);
refStub.withArgs("last_booking_code").returns({transaction: resolveStub});

firestore cloud function, way to delete chat messages

I`m studying cloud function right now and I saw this sample code
'use strict';
const functions = require('firebase-functions');
// Max number of lines of the chat history.
const MAX_LOG_COUNT = 5;
// Removes siblings of the node that element that triggered the function
if there are more than MAX_LOG_COUNT.
// In this example we'll keep the max number of chat message history to
MAX_LOG_COUNT.
exports.truncate =
functions.database.ref('/chat/{messageid}').onWrite(async (change) => {
const parentRef = change.after.ref.parent;
const snapshot = await parentRef.once('value');
if (snapshot.numChildren() >= MAX_LOG_COUNT) {
let childCount = 0;
const updates = {};
snapshot.forEach((child) => {
if (++childCount <= snapshot.numChildren() - MAX_LOG_COUNT) {
updates[child.key] = null;
}
});
// Update the parent. This effectively removes the extra children.
return parentRef.update(updates);
}
return null;
});
how do I convert this to Firestore version from RTDB?
thank you

How to Count Users with a Firebase Cloud Function (getting Function Returned Undefined error)

I have a Firebase Cloud Function that assigns a number to a user on onWrite. The following code works but something is wrong because the console logs state Function returned undefined, expected Promise or value.
I'm also not sure how to refer to the root from inside the onWrite so I've created several "parent" entries that refer to each other. I'm sure there is a better way.
onWrite triggers on this:
/users/{uid}/username
The trigger counts the children in /usernumbers and then writes an entry here with the uid and the child count + 1:
/usernumbers/uoNEKjUDikJlkpLm6n0IPm7x8Zf1 : 5
Cloud Function:
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.setCount = functions.database.ref('/users/{uid}/username').onWrite((change, context) => {
const uid = context.params.uid;
const parent1 = change.after.ref.parent; //uid
const parent2 = parent1.ref.parent; //users
const parent3usernumbers = parent2.ref.parent.child('/usernumbers/');
const parent3usernumbersuid = parent2.ref.parent.child('/usernumbers/'+uid);
parent3usernumbers.once("value")
.then(function(snapshot) {
var a = snapshot.numChildren();
return parent3usernumbersuid.transaction((current) => {
return (a + 1);
}).then(() => {
return console.log('User Number Written', uid, a);
});
});
});
Is there a better way to do this? How can I get the Function Returned Undefined error to go away?
I should also mention it takes a few seconds for the 'usernumber' entry to be written. I'm guessing it's waiting for the function to return something.
Your function have to return a Promise :
exports.setCount = functions.database.ref('/users/{uid}/username').onWrite((change, context) => {
const uid = context.params.uid;
const parent1 = change.after.ref.parent; //uid
const parent2 = parent1.ref.parent; //users
const parent3usernumbers = parent2.ref.parent.child('/usernumbers/');
const parent3usernumbersuid = parent2.ref.parent.child('/usernumbers/'+uid);
return new Promise((resolve, reject) => {
parent3usernumbers.once("value").then(function(snapshot) {
var a = snapshot.numChildren();
return parent3usernumbersuid.transaction((current) => {
return (a + 1);
}).then(() => {
console.log('User Number Written', uid, a);
resolve({uid : uid, a : a})
}).catch(function(e) {
reject(e)
})
});
});
});

firestore cloud functions onCreate/onDelete sometimes immediately triggered twice

I have observed this behavior occasionally with both onCreate and onDelete triggers.
Both the executions happened for the same document created in firestore. There's only one document there so I don't understand how it could trigger the handler twice. the handler itself is very simple:
module.exports = functions.firestore.document('notes/{noteId}').onCreate((event) => {
const db = admin.firestore();
const params = event.params;
const data = event.data.data();
// empty
});
this doesn't happen all the time. What am I missing?
See the Cloud Firestore Triggers Limitations and Guarantees:
Delivery of function invocations is not currently guaranteed. As the
Cloud Firestore and Cloud Functions integration improves, we plan to
guarantee "at least once" delivery. However, this may not always be
the case during beta. This may also result in multiple invocations
for a single event, so for the highest quality functions ensure that
the functions are written to be idempotent.
There is a Firecast video with tips for implementing idempotence.
Also two Google Blog posts: the first, the second.
Based on #saranpol's answer we use the below for now. We have yet to check if we actually get any duplicate event ids though.
const alreadyTriggered = eventId => {
// Firestore doesn't support forward slash in ids and the eventId often has it
const validEventId = eventId.replace('/', '')
const firestore = firebase.firestore()
return firestore.runTransaction(async transaction => {
const ref = firestore.doc(`eventIds/${validEventId}`)
const doc = await transaction.get(ref)
if (doc.exists) {
console.error(`Already triggered function for event: ${validEventId}`)
return true
} else {
transaction.set(ref, {})
return false
}
})
}
// Usage
if (await alreadyTriggered(context.eventId)) {
return
}
In my case I try to use eventId and transaction to prevent onCreate sometimes triggered twice
(you may need to save eventId in list and check if it exist if your function actually triggered often)
const functions = require('firebase-functions')
const admin = require('firebase-admin')
const db = admin.firestore()
exports = module.exports = functions.firestore.document('...').onCreate((snap, context) => {
const prize = 1000
const eventId = context.eventId
if (!eventId) {
return false
}
// increment money
const p1 = () => {
const ref = db.doc('...')
return db.runTransaction(t => {
return t.get(ref).then(doc => {
let money_total = 0
if (doc.exists) {
const eventIdLast = doc.data().event_id_last
if (eventIdLast === eventId) {
throw 'duplicated event'
}
const m0 = doc.data().money_total
if(m0 !== undefined) {
money_total = m0 + prize
}
} else {
money_total = prize
}
return t.set(ref, {
money_total: money_total,
event_id_last: eventId
}, {merge: true})
})
})
}
// will execute p2 p3 p4 if p1 success
const p2 = () => {
...
}
const p3 = () => {
...
}
const p4 = () => {
...
}
return p1().then(() => {
return Promise.all([p2(), p3(), p4()])
}).catch((error) => {
console.log(error)
})
})
Late to the party, I had this issue but having a min instance solved the issue for me
Upon looking #xaxsis attached screenshot, my function took almost the amount of time about 15 seconds for the first request and about 1/4 of that for the second request

Resources