We know Promises are microtasks and they are added on microtask queue and event listeners/timers are tasks and so they are added on task queue. But what about the event-listeners/timers if they are inside of a promise function(which is passed on the creation of promise to promise constructor). In which queue are they get added?
console.log("script start");
var promise = new Promise(function (resolve, reject) {
setTimeout(function () {
console.log("setTimeout2");
resolve();
}, 0);
});
promise.then(function () {
console.log("promise2");
});
setTimeout(function () {
console.log("setTimeout");
}, 0);
console.log("script end");
Here "script start" is the part of main task so it gets executed/printed.
Then the callback function of the promise is handled to browser, the handler adds this to the microtask queue.
the then() pass the callback to the handler of the browser and it will execute after the main promise is resolved.
The setTimeout which is outside of the promise is added to the task queue.
The "script end" gets printed.
Now there are a microtask on the microtask queue and a task on task queue.
So the microtask will be executed first as they have higher priority than the task.
But inside the task, there is a timer.
We know the timers are added to the task queue. But here I think the timer is added to the microtask queue. Because if it was added to task queue then the "setTimeout2" would be printed later because on the task queue there is already a task which prints "setTimeout".
So what actually happens there?
There is nothing special about being "in a promise". The only functions that are put on the microtask queue are the callbacks passed into then.
Here's what's actually happening:
The script start log is executed
The promise is created and calls its executor callback. The executor callback calls setTimeout, which schedules a timer.
The promise is assigned to the promise variable
The then() method is called and attaches a fulfillment callback to the promise
The second setTimeout is called and schedules a second timer
The script end log is executed
This all happened synchronously in one task execution. After some time, the timers fire and (only then!) add their (macro) tasks to the timer event queue. The browser handles the first one:
The setTimeout2 log is executed
The resolve() function is executed which fulfills the promise and adds jobs to run the fulfillment callbacks to the microtask queue
After this macrotask is done, the tasks in the microtask queue are handled:
The promise2 log is executed
Since the microtask queue is empty already, the next macrotask can be handled:
The setTimeout log is executed
Related
I've got a Google Cloud app with several cloud functions that call an API, then save the response in Firebase. I have this scheduled function set up to retry the API on error and it works great. But I want to retry the call if the data I need isn't there yet. Calling again right away could work if I throw an error, but it's highly unlikely that the missing data will be available seconds later, so I'd rather check once an hour after that until I have valid data.
Below is a shortened version of my function. I can imagine adding a setTimeout and having the function call itself again, but I'm not sure I would do that or if it's a good idea since it would keep the function alive for a long time. Is there a way to automatically retry this scheduled function on an arbitrary time interval?
exports.fetchData= functions.pubsub
.schedule('every Tuesday 6:00')
.timeZone('America/New_York')
.onRun(async context => {
const response = fetch(...)
.then(res => {
if (res.status < 400) {
return res;
} else {
throw new Error(`Network response was not ok. ${res}`);
}
})
.then(res => res.json());
const resObj = await response;
resObj.map(x => {
// check response for valid data
})
if (// data is valid) {
// save to Firebase
} else {
// retry in 1 hour
}
});
});
Scheduled functions only run on the schedule you specify. There is no "arbitrary" scheduling. If you think that the function might frequently fail, consider just increasing the frequency of the schedule, and bail out of function invocations that don't need to run because of recent success.
If you enable retries, and the function generates an error by throwing an exception, returning a rejected promise, or timing out, then Cloud Functions will automatically retry the function on a schedule that you can't control.
setTimeout is not a feasible option to keep a function alive for longer than its configured timeout. Cloud Functions will terminate the function and all of its ongoing work after the timeout expires (and you would be paying for the time the function sits idle, which is kind of a waste).
I have a Xamarin.Forms App which use Azure App Service with SQLLite Offline Sync.
When calling SyncContext.InitializeAsync a deadlock appears and the function InitializeAsync never finished.
In this Thread in found the solution: Azure/Xamarin Mobile App Hangs at SyncContext.InitializeAsync
This works:
System.Threading.Tasks.Task.Run(() => this.IDataProvider.IMobileServiceClient.SyncContext.InitializeAsync(localStore, _syncHandler)).Wait();
This not:
await this.IDataProvider.IMobileServiceClient.SyncContext.InitializeAsync(localStore, _syncHandler);
Whole function:
public override async System.Threading.Tasks.Task Init()
{
string storePath = System.IO.Path.Combine(Microsoft.WindowsAzure.MobileServices.MobileServiceClient.DefaultDatabasePath, localStoreName);
Microsoft.WindowsAzure.MobileServices.SQLiteStore.MobileServiceSQLiteStore localStore = new Microsoft.WindowsAzure.MobileServices.SQLiteStore.MobileServiceSQLiteStore(storePath);
localStore.DefineTable<CPM.Recruitment.Mobile.Freelancer.DataObjects.Entities.Promoter>();
System.Threading.Tasks.Task.Run(() => this.IDataProvider.IMobileServiceClient.SyncContext.InitializeAsync(localStore, _syncHandler)).Wait();
//await this.IDataProvider.IMobileServiceClient.SyncContext.InitializeAsync(localStore, _syncHandler);
_promoters = new Azure.AppService.DataObjects.Client.Sync.List<CPM.Recruitment.Mobile.Freelancer.DataObjects.Entities.Promoter>(this, this.IDataProvider.IMobileServiceClient.GetSyncTable<CPM.Recruitment.Mobile.Freelancer.DataObjects.Entities.Promoter>());
}
But why? I dont want to use Wait();
The differences between await and Wait method are:
await means the current task will be executed in a separated thread asynchronously and the calling thread won't be blocked. If the calling thread is UI thread. the UI thread will continue to run other operations.
Wait method are synchronization method. It causes the calling thread to wait until the current task has completed. If the calling thread is UI thread, UI operations will be blocked.
It seems that a deadlock will be caused if you use await to run the task which will not block UI thread.
I've created a very simple server using Meteor, to send an email after a timeout. When I use a timeout, the message is successfully sent but an error is thrown: [Error: Can't wait without a fiber].
Here's my code:
if (Meteor.isServer) {
Meteor.startup(function () {
// <DUMMY VALUES: PLEASE CHANGE>
process.env.MAIL_URL = 'smtp://me%40example.com:PASSWORD#smtp.example.com:25';
var to = 'you#example.com'
var from = 'me#example.com'
// </DUMMY>
//
var subject = 'Message'
var message = "Hello Meteor"
var eta_ms = 10000
var timeout = setTimeout(sendMail, eta_ms);
console.log(eta_ms)
function sendMail() {
console.log("Sending...")
try {
Email.send({
to: to,
from: from,
subject: subject,
text: message
})
} catch (error) {
console.log("Email.send error:", error)
}
}
})
}
I understand that I could use Meteor.wrapAsync to create a fiber. But wrapAsync expects there to be a callback to call, and Email.send doesn't use a callback.
What should I do to get rid of the error?
This happens because while your Meteor.startup function runs inside a Fiber (like almost all other Meteor callbacks), the setTimeout you use does not! Due to the nature of setTimeout it will run on the top scope, outside the fiber in which you defined and/or called the function.
To solve, you could use something like Meteor.bindEnvironment:
setTimeout(Meteor.bindEnvironment(sendMail), eta_ms);
And then do so for every single call to setTimeout, which is a painfully hard fact.
Good thing it's not actually true. Simply use Meteor.setTimeout instead of the native one:
Meteor.setTimeout(sendMail, eta_ms);
From the docs:
These functions work just like their native JavaScript equivalents. If you call the native function, you'll get an error stating that Meteor code must always run within a Fiber, and advising to use Meteor.bindEnvironment
Meteor timers just bindEnvironment then delay the call as you wanted.
Here are two simple examples. No unnecessary. They do not work. Error is output to the console.
No asynchronous tests are working. These - not.
Tests write for meteor application.
About jasmine.DEFAULT_TIMEOUT_INTERVAL I already know, is the 5000.
describe("Jasmine async tests", function(){
it("First test with timeout", function(done){
Meteor.setTimeout(function(){
done();
}, 300);
});
it("Second test, request to google.com", function(done){
HTTP.get("http://google.com/", {}, function(){
done();
});
});
});
Output to the console:
I20141021-21:08:35.178(3)? Async Login Test response to google.com
I20141021-21:08:35.179(3)? Error: Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
I20141021-21:08:35.180(3)? at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)
In unit test mode most of the Meteor API is stubbed out. That means that calling Meteor.setTimeout or HTTP.get will just execute an empty function and therefore will do nothing. Your callback is never called.
You can find the used stubs here: https://github.com/meteor-velocity/meteor-stubs/blob/master/index.js
Also relevant: https://github.com/Sanjo/meteor-jasmine/issues/55#issuecomment-61026304
Why this code shows "0"? Shouldn't it return "1"?
Messages = new Meteor.Collection("messages");
if (Meteor.is_client) {
Meteor.startup(function () {
alert(Messages.find().count());
});
}
if (Meteor.is_server) {
Meteor.startup(function () {
Messages.insert({text: "server says hello"});
});
}
If I do the "Messages.find().count()" later, it returns 1.
By default, when a Meteor client starts up, it connects to the server and subscribes to documents in any Meteor.Collection you defined. That takes some time to complete, since there's always some amount of delay in establishing the server connection and receiving documents.
Meteor.startup() on the client is a lot like $() in jQuery -- it runs its argument once the client DOM is ready. It does not wait for your client's collections to receive all their documents from the server. So the way you wrote the code, the call to find() will always run too early and return 0.
If you want to wait to run code until after a collection is first downloaded from the server, you need to use Meteor.subscribe() to explicitly subscribe to a collection. subscribe() takes a callback that will run when the initial set of documents are on the client.
See:
meteor-publish
and meteor-subscribe
Just to follow up with a code example of how to know when a collection is ready to use on the client.
As #debergalis described, you should use the Meteor.subscribe approach - it accepts a couple of callbacks, notably onReady
For example:
if(Meteor.isClient){
Meteor.subscribe("myCollection", {
onReady: function(){
// do stuff with my collection
}
});
}