I'm going through Kyle Simpson's Rethinking Async on Frontend Masters, and in the lecture on async generators, there is an example of calling .next() recursively. The gist of it goes as follows:
function *main() {
var x = (yield setTimeout(function() {return a.next(2)},0));
console.log(x)
}
var a = main();
a.next();
Which prints 2
Correct me if I'm wrong. What I understand from this is that the encounter with yield pauses execution and waits for a value. The functionRef from setTimeout is placed on the stack and is called next, returning the value 2.
Essentially this is the same as
function *main() {
var x = yield ;
console.log(x);
}
var a = main();
a.next()
a.next(2)
Now I try to do the following, thinking the result will be the same:
function *main() {
var x = (yield a.next(2));
console.log(x)
}
var a = main();
a.next();
and it throws the error TypeError: Generator is already running
Why?
Further, I expected this to print 2 but instead in prints undefined
function *main() {
var x = yield 2;
console.log(x)
}
var a = main();
a.next()
a.next()
Evidently, yield behaves rather a bit differently when assigned to a variable versus when it is not, but I can't quite put a finger on it.
Related
I am trying the async examples from the GNOME project site. I get the follwoing warning which I don't under stand on how to fix.
async.vala:8.2-8.17: warning: delegates with scope="async" must be owned
Code
async double do_calc_in_bg(double val) throws ThreadError {
SourceFunc callback = do_calc_in_bg.callback;
double[] output = new double[1];
// Hold reference to closure to keep it from being freed whilst
// thread is active.
// WARNING HERE
ThreadFunc<bool> run = () => {
// Perform a dummy slow calculation.
// (Insert real-life time-consuming algorithm here.)
double result = 0;
for (int a = 0; a<100000000; a++)
result += val * a;
output[0] = result;
Idle.add((owned) callback);
return true;
};
new Thread<bool>("thread-example", run);
yield;
return output[0];
}
void main(string[] args) {
var loop = new MainLoop();
do_calc_in_bg.begin(0.001, (obj, res) => {
try {
double result = do_calc_in_bg.end(res);
stderr.printf(#"Result: $result\n");
} catch (ThreadError e) {
string msg = e.message;
stderr.printf(#"Thread error: $msg\n");
}
loop.quit();
});
loop.run();
}
The warning is pointing at the run variable inside the async function. Who or what needs to be owned? The reference to the closure?
The delegate needs to have a well defined owner all the time. The error message is a bit misleading.
To fix it you have to explicitly transfer the ownership from the delegate to the thread constructor:
new Thread<bool>("thread-example", (owned) run);
Instead of
new Thread<bool>("thread-example", run);
See also: https://wiki.gnome.org/Projects/Vala/Tutorial#Ownership
PS: The generated C code is fine in both cases. (at least with valac 0.46.6)
I am having problems with futures in a Flutter app.
void saveCats() async {
var cats = await convertToCats(_rawData);
await DatabaseProvider.db.addCats(cats);
}
Future<List<Cat>> convertToCats(CatList catData) async {
var cats = <Cat>[];
await catData.forEach(key, value) async {
var pos = await calculatePos();
print('This should come first');
cats.add(Cat(name: key, pos: pos);
}
}
Future<int> calculatePos() async {
return await DatabaseProvider.db.calculatePos();
}
database.dart:
Future<void> addCats(List<Cat> cats) async {
print('This should come second');
// Do adding stuff here
}
Future<int> calculatePos() async {
// Some code to calculate the position
return pos;
}
In the above code, the saveCats function is called when a button is tapped. This function converts some raw data to a list of Cat models, and adds them to the database. As part of this conversion process, it calculates the pos of the cat in a list. My problem is that I would expect that, of my two print statements, the one in the forEach loop should come before the one in the addCats database function. But instead they appear in reverse order. Where am I going wrong?
You can't async/await in List.forEach() or Map.forEach() as both of them return void.
Either use
await Future.forEach([1, 2, 3], (num) async {
await asyncMethod(num);
});
or something similar;
https://api.flutter.dev/flutter/dart-async/Future/forEach.html
forEach often doesn't do what you expect, because the provided function runs as a closure.
It's more natural when you want to iterate over a list doing something to each element to use for (or one of the more functional type methods like map).
It's not clear what type CatList is, so this is approximate, but you'll want something more like:
Future<List<Cat>> convertToCats(CatList catData) async {
var cats = <Cat>[];
for (var i = 0; i < catData.length; i++) {
var key = catData[i].key;
var pos = await calculatePos();
print('This should come first');
cats.add(Cat(name: key, pos: pos));
}
return cats;
}
or
Future<List<Cat>> convertToCats(CatList catData) async {
return catData.map(...some mapping function...).toList();
}
I have the following dart code in the latest Dart dev release (SDK 1.9.0-dev.8.0):
main() async {
var i = await testMethod(1);
var j = await testMethod(2);
var k = await testMethod(3);
}
int testMethod (int x) {
var breakpoint = 'breakpoint is set here';
print(x);
return(x);
}
When I put a breakpoint in the first statement of the testmethod, the breakpoint only gets hit for the second call and third call, never for the first call. The console prints 1, then the debugger stops in at the breakpoint for the second call.
Is this still a bug in the async/await debugging or am I missing something here?
I need a function that emits individual lines from a file with newlines. Nothing hard.
But with node, it is hard, and with Meteor, there's an additional complication: you must use Meteor.wrapAsync. Surprisingly, there isn't an example of how to use wrapAsync in the docs, and I could only find a couple of examples online, none of which helped.
I have something like:
var readFileAsync = function (file, cb) {
// From here to below comment works synchronously
var instream = fs.createReadStream(file, function () {
var outstream = new stream;
outstream.readable = true;
outstream.writable = true;
var rl = readline.createInterface({
input: instream,
output: outstream,
terminal: false
});
rl.on('line', function(line) {
console.log(line);
return line;
});
});
// Reference to aforementioned comment
};
var readWatFile = Meteor.wrapAsync(readFileAsync);
var line = readWatFile('/path/to/my/file');
console.log(line);
I know this is wrong because it doesn't work, so how do I write this?
There are two ways to go around it.
Load the whole file into memory and do whatever you want. To do that you can use the Private Assets API
Use node.js streams and stream the file line by line. You would have something like this.
Example code that you would need to tweak to your favorite streaming methods:
var Future = Npm.require('fibers/future');
var byline = Npm.require('byline');
var f = new Future;
// create stream in whatever way you like
var instream = fs.createReadStream(...);
var stream = byline.createStream(instream);
// run stream handling line-by-line events asynchronously
stream.on('data', Meteor.bindEnvironment(function (line) {
if (line) console.log(line)
else future.return();
}));
// await on the future yielding to the other fibers and the line-by-line handling
future.wait();
I want to do further processing depending on the success or failure of the set() method, but I need the context of some objects at the time I call the set() method. Otherwise my objects will be out of scope when the oncomplete function is called unless I put them in global - which I don't really want to do.
Here is an example:
function oncomplete_AddTran(tran,client,appt,balance){
/* if named argument 'balance' exists it is safe to assume
Firebase has not 'stepped on' the arguments with it's single
Error object or null */
if(balance typeof object) console.log("my parameters made it here");
}
function addTran(tran, client, appt, balance) {
var TRANS_LOCATION = 'https://xxx.firebaseIO.com/testing/transactions';
var tranListRef = new Firebase(TRANS_LOCATION);
var oncomplete = function() {
oncomplete_AddTran(tran, client, appt, balance); };
var tranref = tranListRef.child(tran.name).set(tran.literal, oncomplete);
}
Yes, it is possible. I am too impatient waiting for the confirmation I was looking for and decided to test myself. Here is the code I used (that works for my purpose):
function oncomplete_AddTran(tran,client,appt,balance){
console.log("arguments passed:" + arguments.length);
// firebase original arguments :: arguments.callee.caller.arguments
var fbargs = arguments.callee.caller.arguments;
}
function addTran(tran, client, appt, balance) {
var TRANS_LOCATION = "https://xxx.firebaseIO.com/testing/transactions";
var tranListRef = new Firebase(TRANS_LOCATION);
var oncomplete = function() {
oncomplete_AddTran(tran, client, appt, balance); };
var tranref = tranListRef.child(tran.name).set(tran.literal, oncomplete);
}
function main() {
var tran = {}; tran.name = "test1"; tran.literal = { tran: "tran" };
var client = {}; client.literal = { client: "client" };
var appt = {}; appt.literal = { appt:"appt" };
var balance = {}; balance.literal = { balance:"balance" };
addTran(tran,client,appt,balance);
}
The arguments were passed as expected but I still don't know how Firebase's set() method will pass the error object to the callback (in the event of an error) because I haven't tried reproducing an error and don't really know if I can.
The default null, and another (undefined) that is supposed to be passed when there is no error is not found in arguments.callee.caller.arguments (see callback function in example above). I am not sure that what I am doing is good practice - seems a bit hacky to me so I won't accept this answer to the question (yet).