Consider this (textbook) sample dart code:
// Sequential processing using async and await.
main() async {
await expensiveA();
await expensiveB();
doSomethingWith(await expensiveC());
}
I understand that await will wait for the result of the future "expensiveA" to complete before continuing.
But what I don't understand is why main() must itself be declared async? Apparently you have to do this or the program will not compile. But why can a synchronous method not await the result of asynchronous calls?
I understand this must be a newbie question but I have not been able to find an answer for this yet.
Before async/await, which was only added a while ago to Dart, you would have written like
main() {
var result = expensiveA()
.then((_) => expensiveB())
.then((_) => expensiveC()); // the Future returned by expensiveC will be returned because `doSomethingWith` is not awaited in your code
result.then(doSomethingWith);
return result;
}
An async call returns a Future, and you can pass a function to it's then method to be called when the async call completes.
This code
main() async {
await expensiveA();
await expensiveB();
doSomethingWith(await expensiveC()); // doSomethingWith is not awaited
}
get's rewritten to something similar to the code in the first code block, before it's executed.
This shows that async/await doesn't change the nature of the code, it only changes the syntax to something that looks more like sync code.
async is a marker that the code in the method needs to be rewritten, and this marker allows the use of await, without rewriting await would have no meaning.
async implicitly returns the last awaited Future, so that the caller is able to await the completion of this Future.
Because a Future is returned, strong mode requires the return type to match.
Why can a synchronous method not await the result of asynchronous calls?
Because that would mean blocking until expensive call is finished.
However Dart is single threaded with event loop, so having easy way to block whole Dart app would be extremely dangerous.
Related
I have written a handler function inside my nextjs page/api folder;
handler(req, res) {}
Am using #influxdata/influxDb-client as mentioned in the documentation. Am using
from(queryAPI.rows(query).pipe(....).subscribe(next(value)=> {results.push(value}, complete(console.log(results); res.status(200).json(results)}
Am getting all the query value, once the observable is completed. it works most of the time.
Am pushing the intermediate results in the next part of the subscriber and trying to send the results back to client in the complete part of the subscriber. I want the request handler to wait till i get all my values from influx DB query in the complete part of the subscriber and can send the value back to client..
But the issue "Handler function will not Wait till the observable is completed". Handler function returns, before the observer gets completed. Am getting error: API resolved without sending a response...
I get all the values only when the observer is completed.
I don't know how to handle the scenario.
How can I make the handler function wait until the observable is completed?
I found the solution for the same
I used new Promise() with await, added my observable inside this promise and resolved the promise on Complete of the subscribe.
Code will look like the following :
export async function handler (req, res) {
const results=[];
await new Promise((resolve, reject) => {
from((queryAPIs.rows(query))
.pipe(map(({values, tableMeta}) => tableMeta.toObject(values)))
.subscribe(
{
next(object) => {results.push(object)}
complete() => { resolve (results) }
error(err) => { reject (err) }
});
res.status(200).send(results);
}
}
I am a relative beginner with Dart & right now for my Flutter app I am unsure on how to properly subscribe to multiple topics at once to Firebase Cloud Messaging. I want to receive notifications whenever my Cloud Function sends notifications through FCM to these topics at a certain time interval. The FlutterFire documentation seems to say that the subscribe/unsubscribeToTopic methods return Futures, but what is the best way in Dart to resolve multiple futures? So far, I have thought of 2 options below:
Method 1
Future<void> subscribeTopicV1(List<int> selectedUsers) async {
for (var userId in selectedUsers) {
await messaging.subscribeToTopic('$userId');
}
}
My understanding is this basically turns all the subscribe/unsubscribe topic operations from asynchronous to synchronous if in the parent I call subscribeTopicV1 like the following:
...
if (shouldSubscribe) {
await subscribeTopicV1
}
...
// OR
if (shouldSubscribe) {
subscribeTopicV1.then( () => {
... // do whatever
})
}
This should mean that I am waiting on each messaging.subscribeToTopic to return before I move on to subscribe the next topic?
Method 2
Future<void> subscribeTopicV2(List<int> selectedUsers) async {
Future.wait(selectedUsers
.map((userId) => messaging.subscribeToTopic('$userId')));
}
Now this second method would run all the messaging.subscribeToTopic operations in parallel and if I call subscribeTopicV2 in the same way like:
...
if (shouldSubscribe) {
await subscribeTopicV2
}
...
// OR
if (shouldSubscribe) {
subscribeTopicV2.then( () => {
... // do whatever
})
}
In this second method, I should be waiting for when all futures are resolved inside subscribeTopicV2? I would like to mention that I do not care about the order in which the topics are subscribed so is it fair to say in this case I should be going with Method 2 for optimal performance since that it allows me to execute those async operations in parallel? I can't find any documentation of how people deal with subscribing to multiple topics so any insight would be much appreciated!
I'm trying to make a version control for my application. In other words, trying to connect to firebase documents, pick version number from there and compare it with value from shared preferences in order to decided if it's required to update the information.
I've followed the official tutorial on Futures.
here is my code:
#override
void initState(){
loadit();
}
Future _getIntVersionFromSharedPref() async {
await Firestore.instance.collection('DataIndex')
.document('dbversion').snapshots().listen((event) {
setState(() {
fbVersion = event['currentversion'];
});
print('_getIntVersionFromSharedPref() future -> $fbVersion');
return fbVersion;
});
}
Future<String> loadit() async {
var myFBversion = await _getIntVersionFromSharedPref();
print('loadit future -> $myFBversion');
}
However I endup with the following console output:
I/flutter: loadit future -> null
I/flutter: _getIntVersionFromSharedPref() future -> 1
Why would it fire null right away without waiting for the value? According to the official tutorial it should be executed in turn.
listen() doesn't return a Future, so you can't await it. It returns a StreamSubscription object asynchronously, and you must handle the ongoing results of the query within your stream handler function. When you are done with the Stream, you should unsubscribe from the stream using the StreamSubscription.
If you just want a single snapshot of the document, don't use snapshots() at all. Just use get() to get a single snapshot as shown in the documentation.
Listen isn't part of the dart Future API, so that listen callback isn't tied to the Promise that is implicitly returned from _getIntVersionFromSharedPref.
I think you need to manually return a Promise from that method, and resolve it from your listen callback. You can manually resolve a Future like this with the Completer class.
Something like this:
Future _getIntVersionFromSharedPref() async {
var completer = Completer<num>();
await Firestore.instance.collection('DataIndex')
.document('dbversion').snapshots().listen((event) {
setState(() {
fbVersion = event['currentversion'];
});
print('_getIntVersionFromSharedPref() future -> $fbVersion');
completer.complete($fbVersion);
});
// This will be returned right away, but anything that awaits it will block
// until the callback above has completed the future.
return completer.future;
}
Suppose I have 2 collections "PlanSubscriptions" and "ClientActivations". I am serially doing a insert on both the collections.
Later one depends on previous one, if any of the transaction fails then the entire operation must rollback.
How can I achieve that in Meteor 1.4?
Since MongoDB doesn't support atomicity, you will have to manage it with Method Chaining.
You can write a method, say, transaction where you will call PlanSubscriptions.insert(data, callback). Then in the callback function you will call ClientActivations.insert(data, callback1) if the first insertion is success and in callback1 return truthy if second insertion is succes, otherwise falsy. If the first insertion returns error you don't need to do anything, but if the second insertion returns error then remove the id got from the insertion in first collection.
I can suggest following structure:
'transaction'(){
PlanSubscriptions.insert(data, (error, result)=>{
if(result){
// result contains the _id
let id_plan = result;
ClientActivations.insert(data, (error, result)=>{
if(result){
// result contains the _id
return true;
}
else if(error){
PlanSubscriptions.remove(id_plan);
return false;
}
})
}
else if(error){
return false;
}
})
}
There is no way to do that in Meteor, since mongodb is not an ACID-compliant database. It has a single-document update atomicity, but not a multiple-document one, which is your case with the two collections.
From the mongo documentation:
When a single write operation modifies multiple documents, the modification of each document is atomic, but the operation as a whole is not atomic and other operations may interleave.
A way to isolate the visibility of your multi-document updates is available, but it's probably not what you need.
Using the $isolated operator, a write operation that affects multiple documents can prevent other processes from interleaving once the write operation modifies the first document. This ensures that no client sees the changes until the write operation completes or errors out.
An isolated write operation does not provide “all-or-nothing” atomicity. That is, an error during the write operation does not roll back all its changes that preceded the error.
However, there are a couple of libraries which try to tackle the problem at the app-level. I recommend taking a look at fawn
In your case, where you have exactly two dependent collections, it's possible to take advantage of the two phase commits technique. Read more about it here: two-phase-commits
Well I figured it out myself.
I added a package babrahams:transactions
At server side Meteor Method call, I called tx Object that is globally exposed by the package. The overall Server Side Meteor.method({}) looks like below.
import { Meteor } from 'meteor/meteor';
import {PlanSubscriptions} from '/imports/api/plansubscriptions/plansubscriptions.js';
import {ClientActivations} from '/imports/api/clientactivation/clientactivations.js';
Meteor.methods({
'createClientSubscription' (subscriptionData, clientActivationData) {
var txid;
try {
txid = tx.start("Adding Subscription to our database");
PlanSubscriptions.insert(subscriptionData, {tx: true})
ClientActivations.insert(activation, {tx: true});
tx.commit();
return true;
} catch(e){
tx.undo(txid);
}
return false;
}
});
With every insert I had added {tx : true}, this concluded it to be a apart of transaction.
Server Console Output:
I20170523-18:43:23.544(5.5)? Started "Adding Subscription to our database" with
transaction_id: vdJQvFgtyZuWcinyF
I20170523-18:43:23.547(5.5)? Pushed insert command to stack: vdJQvFgtyZuWcinyF
I20170523-18:43:23.549(5.5)? Pushed insert command to stack: vdJQvFgtyZuWcinyF
I20170523-18:43:23.551(5.5)? Beginning commit with transaction_id: vdJQvFgtyZuWcinyF
I20170523-18:43:23.655(5.5)? Executed insert
I20170523-18:43:23.666(5.5)? Executed insert
I20170523-18:43:23.698(5.5)? Commit reset transaction manager to clean state
For more Information you can goto link : https://github.com/JackAdams/meteor-transactions
NOTE: I am using Meteor 1.4.4.2
Just sharing this link for future readers:
https://forums.meteor.com/t/solved-transactions-with-mongodb-meteor-methods/48677
import { MongoInternals } from 'meteor/mongo';
// utility async function to wrap async raw mongo operations with a transaction
const runTransactionAsync = async asyncRawMongoOperations => {
// setup a transaction
const { client } = MongoInternals.defaultRemoteCollectionDriver().mongo;
const session = await client.startSession();
await session.startTransaction();
try {
// running the async operations
let result = await asyncRawMongoOperations(session);
await session.commitTransaction();
// transaction committed - return value to the client
return result;
} catch (err) {
await session.abortTransaction();
console.error(err.message);
// transaction aborted - report error to the client
throw new Meteor.Error('Database Transaction Failed', err.message);
} finally {
session.endSession();
}
};
import { runTransactionAsync } from '/imports/utils'; // or where you defined it
Meteor.methods({
async doSomething(arg) {
// remember to check method input first
// define the operations we want to run in transaction
const asyncRawMongoOperations = async session => {
// it's critical to receive the session parameter here
// and pass it to every raw operation as shown below
const item = await collection1.rawCollection().findOne(arg, { session: session });
const response = await collection2.rawCollection().insertOne(item, { session: session });
// if Mongo or you throw an error here runTransactionAsync(..) will catch it
// and wrap it with a Meteor.Error(..) so it will arrive to the client safely
return 'whatever you want'; // will be the result in the client
};
let result = await runTransactionAsync(asyncRawMongoOperations);
return result;
}
});
My client is making a call to the server.
Meteor.call('someRequest', params, onAllDoneCallback);
that is processed by (server code)
Meteor.methods({
'someRequest': function(params, cb) {
anAsyncFunction(params, cb);
return true;
},
});
I'd like the onAllDoneCallback to be triggered on the client side once the anAsyncFunction has finished and triggers its own callback.
However, in Meteor it seems that the second argument of someRequest is ignored and that the onAllDoneCallback is triggered with what someRequest returns, which here is true and which is called before that anAsyncFunction has finished.
In my case I'm more concerned about the timing issue (I'm using it to tell the client that the processing is finished, and not just that the request is well received) but other will probably want to call the callback with arguments from anAsyncFunction
What you are doing now is passing a function to the server. If that does work, it's very insecure. What you want to do is create a future and then use it to manage the asynchronous function. Here is an example:
let Future = Npm.require('fibers/future');
Meteor.methods({
someRequest: function (someArg) {
// for security reasons, make sure you check the type of arguments.
check(someArg, String);
// future is an async handler.
let future = new Future();
// this is a function for a generic node style callback [ie, function (err, res)]
let resolver = future.resolver();
// run your function and pass it the future resolver
// the future will be resolved when the callback happens.
anAsyncFunction(someArg, resolver);
// this meteor method will not end until future has been resolved.
return future.wait();
}
});
Alternatively, Meteor provides a wrapAsync that provides similar functionality of wrapping async functions in futures so they can run in meteor methods. That is:
let wrappedAsyncFunction = Meteor.wrapAsync(anAsyncFunction /** second argument is `this` binding*/);
return wrappedAsyncFunction();
adapting this answer: Meteor: Proper use of Meteor.wrapAsync on server
You have to use Meteor's wrapAsync api, which takes a function that accepts a callback as its last argument, the callback being like function(error, result){}. So it will look like:
Meteor.methods({
'someRequest': function(params){
var wrap = Meteor.wrapAsync(anAsyncFunction);
return wrap(params);
},
});