I have a save function in my app which can be called manually and an autosave function which runs every 60 seconds.
To prevent the two ops trying to access the same file at the same instant, I set a flag called isSaving to true when one starts running, and to false again afterward. If open or save detect that autosave is running, they wait 1000ms and try again. If they fail after that I consider it an error.
Autosave:
setInterval(autosave, 1000 * 60);
isSaving = false;
function autosave()
{
return new WinJS.Promise(function (complete, error, progress)
{
if(isSaving == false) // no saving op in progress
{
// set saving flag on
isSaving = true;
// write file
return writeFile(currentFile)
.then(function () {
// saving flag off
isSaving = false;
complete();
});
}
else {
// silently abort
complete();
}
});
}
Manual save:
var saveFileAttempts = 0;
function save()
{
return new WinJS.Promise(function (complete, error, progress)
{
if (isSaving == false) // no saving op in progress
{
// set saving flag on
isSaving = true;
// write file
return writeFile(currentFile)
.then(function () {
// show notification to user "file saved"
return showSaveNotification()
})
.then(function () {
// set saving flag off
isSaving = false;
complete();
});
}
else if (saveFileAttempts < 10) {
// try again in 1000ms, up to 10 times
saveFileAttempts++;
setTimeout(function () { save(); }, 1000);
}
else{
error();
}
});
}
Open:
var openFileAttempts = 0;
function open()
{
return new WinJS.Promise(function (complete, error, progress)
{
if (isSaving == false)
{
return readFile()
.then(function (file) {
currentFile = file;
openFileAttempts = 0;
complete();
});
}
else if (openFileAttempts < 10) {
// try again in 1000ms, up to 10 times
openFileAttempts++;
setTimeout(function () { open(); }, 1000);
}
else{
error();
}
});
}
This feels like a hack. Is there a better way to achieve what I'm trying to do?
FYI: These functions return promises because there are other functions that call them.
Instead of waiting 1000ms and trying again, I'd recommend using a promise to represent that a save is ongoing and when it will end.
var saving = null;
setInterval(function() {
if (!saving) // ignore autosave when already triggered
save().then(showAutoSafeNotification);
}, 60e3);
function save() {
if (saving)
return saving.then(save); // queue
// else
var written = writeFile(currentFile);
saving = written.then(function() {
saving = null;
}, function() {
saving = null;
});
return written;
}
You can do the same with open (and might want to abstract the written part out), although I fail to see how it interferes with an (auto)save. If you're concerned about reading the file that is already open while it is saved, I'd let the filesystem handle that and catch the error.
how about maintaining a single promise chain. Then, you might not need a setTimeout, this is a easy way, might be flawed, haven't used WinJS, writing code like it is normal promise:
setInterval(save, 1000 * 60);
var promise = Promise.resolve();
function save(){
return promise.then(function(){
return writeFile(currentFile);
});
}
function open(){
return promise.then(function(){
return readFile().then(function (file) {
currentFile = file;
});
});
}
but I guess, one problem with this code is, since it is single promise chain, you need to catch error properly in your application.
Related
I have a web application, in which I have two buttons: "start download" and "abort download", that are bind to the methods: start() and stop() accordingly.
1. When the user clicks the "start download" button, the application will loop over an array of data. For each item in the array, it will call the _downloadFile() method that will download one file and return a new Promise(), once that download is complete the next download will start.
2. If during the download flow (that may take a long long time) the user clicked the "abort download" button I would like to stop the download flow.
How can I implement the abort functionality?
Here is what I got so far?
Notice that due to the fact I'm using async/await, the loop will holt until the promise is resolved, and the next iteration will only be executed once the promise is resolved (or rejected).
async function start(dataArray)
{
for (let i = 0; i < dataArray.length; i++)
{
try
{
let fileUrl = `http://server-url/${ i }/${ dataArray[i] }.xml`;
let result = await _downloadFile(fileUrl);
_saveResult(result);
}
catch(e) // promise rejected
{
_handleError(e);
}
}
}
function stop()
{
// what goes here?? how do I stop the flow??
}
function _downloadFile(fileUrl)
{
return new Promise((resolve, reject) =>
{
// ...
});
}
function _saveFile(data)
{
// ..
}
function _handleError(error)
{
console.error("Error: " + error);
}
Set a flag that the function can check, and throw if it's set. (Well, you're actually converting rejection to resolution in your code, so maybe you don't throw, just return early. [I wouldn't do that, btw.])
let stopped = false; // *** The flag
async function start(dataArray)
{
stopped = false; // *** Re-init the flag
for (let i = 0; i < dataArray.length; i++)
{
try
{
let fileUrl = `http://server-url/${ i }/${ dataArray[i] }.xml`;
let result = await _downloadFile(fileUrl);
if (stopped) { // *** Check the flag
return; // *** and return
}
_saveResult(result);
}
catch(e) // promise rejected
{
_handleError(e);
}
}
}
function stop()
{
// *** Set the flag
stopped = true;
}
The following code fires an alert when it detects fetch() request to a certain endpoint. Doing so makes the request stops from proceeding, waits for the user to close the alert and then lets the request flow to the endpoint.
My question is how to achieve the same interruption, but instead of waiting for the alert to be closed, I'd need the request to wait for the appearance of a cookie. I have a feeling it needs to be done with Promises :)
const x = window.fetch;
window.fetch = function() {
if (arguments[0] == '/certain_endpoint') { alert('stopping for a while'); }
return x.apply(this, arguments)
}
You can use setInterval with promises to periodically poll for a certain condition and resolve when it is met.
const x = window.fetch;
window.fetch = function() {
if (arguments[0] == '/needs_cookie') {
return waitForCookie().then(cookie => {
return x.apply(this, arguments);
});
} else {
return x.apply(this, arguments);
}
}
// Returns a promise that resolves to a cookie when it is set.
function waitForCookie() {
return new Promise(function (resolve, reject) {
var intervalID = setInterval(checkForCookie, 100);
function checkForCookie() {
var cookie = getCookie();
if (cookie) {
clearInterval(intervalID);
resolve(cookie);
}
}
});
}
// Here, getCookie() returns undefined the first two times it's called.
// In reality, it should just parse document.cookie however you normally do.
var attempts = 0;
function getCookie() {
if (attempts < 2) {
attempts++;
console.log('Attempts: ', attempts);
return undefined;
} else {
return 'cookie!';
}
}
If you're trying to do more complicated asynchronous stuff, including polling, you may want to check out RxJS.
{
const original = window.fetch
window.fetch = function(...args) {
if (args[0] == '/certain_endpoint') {
return new Promise(res => {
setTimeout(() => res(original(...args)), 1000);
});
}
return original(...args);
};
}
You may return a promise instead that resolves after some time
I've built a set of functions that checks to see if there are any current notifications scheduled for iOS and if there are, I want to cancel them and reschedule them. The below logic will return if there are scheduled items, and if there are will begin the cancel and reschedule. The problem is, they all show to be successful and console logs to show this, but the reschedule function doesn't appear to be rescheduling them. Is there a way to ensure that they have been removed before trying to reschedule? My only guess is that I try to reschedule before it has completed removal.
if (isIOS){
var getScheduled = function() {
$cordovaLocalNotification.getAllScheduled().then(function (scheduledItems) {
$scope.scheduledContainer = scheduledItems;
$scope.scheduledItems = scheduledItems;
if($scope.scheduledItems.length < 1) {
console.log('no previously scheduled items.')
return;
} else {
cancelAll();
console.log("there are items here.")
}
})
}
getScheduled();
// If there are notifications, cancel and reschedule.
var cancelAll = function() {
console.log('we made it to the cancel function');
// Cancell All
$cordovaLocalNotification.cancelAll().then(function (result) {
console.log('They Cancelled');
rescheduleAll();
});
}
var rescheduleAll = function() {
//Reschedule All
$cordovaLocalNotification.schedule($scope.scheduledItems).then(function() {
console.log('Successfully Scheduled');
});
}
}
Use finally to execute functions after the promise is fullfilled:
var cancelAll = function() {
console.log('we made it to the cancel function');
// Cancell All
$cordovaLocalNotification.cancelAll().then(function (result) {
// This is the success handler
}, function(err) {
// This is the error handler
}).finally(function() {
console.log('They Cancelled');
rescheduleAll();
});
}
Couldn't you use a .success promise and link the functions?
var function1 = function() {
//do something
}.success(function () {
function2();
}
var function2 = function () {
//do something
}
I have a web worker that's running a long-running calculation. I've split the calculation up to run in a chain of Promises, so several times per second, the thread has a chance to do something else before continuing with the calculation.
I want to be able to cancel the calculation by sending a message. Using Worker.terminate() kills the calculation, but doesn't free up the memory, and I'm running into browser crashes.
function calculate() {
// case when no dice are being calculated
if (!_diceList.length) {
_finished = true;
return Promise.resolve();
}
let promise = Promise.resolve();
// create chain of promise calculations
for (let i = 0; i < _totalPermutations; i += PERMUTATIONS_PER_BREAK) {
promise = promise.then(() => {
if (!_shouldContinue || _finished) {
return;
} else {
reportStatus();
_atBreak = false;
calculateRecursive();
}
});
}
return promise;
}
// ...
function run(data) {
setup(data.dice);
calculate().then(() => {
reportStatus();
}).catch(() => true).then(() => {
close();
});
}
onmessage = function (e) {
if (e.data === 'cancel') {
_shouldContinue = false;
} else {
run(e.data);
}
};
When I debug, I can see that the code that creates the Worker does send the 'cancel' message, but when I set a breakpoint in my onmessage in the Worker code, it never enters the function.
I know that JavaScript is single-threaded, but I thought the entire purpose of Promises was to simulate async behavior.
I have a small library with a single API function, start().
Once started, it should check a URL every 2 seconds and after some time the url-checker will resolve.
But I don't know how to implement the repeated setTimeout for a deferred function..I tried variations where the checkProgress() calls itself but then the promise isn't returned anymore.
Here's the code:
Lib.progressChecker = (function($) {
'use strict';
var api = {};
var checkProgress = function (url) {
var d = $.Deferred();
$.get(url).done(function(foo) {
if (foo === 'bar') {
//script is not finished yet
} else {
//finished, resolve and stop looping
d.resolve();
}
});
return d.promise();
};
api.start = function(projectId) {
var url = 'foobar/'+projectId;
var d = $.Deferred();
setTimeout(function(){
checkProgress(url).done(function () {
d.resolve();
});
}, 2000);
return d.promise();
};
return api;
}) (jQuery);
You can do it like this where you just resolve the first deferred when you see the $.get() returns the desired value and if not, you run it again:
Lib.progressChecker = (function($) {
'use strict';
var api = {};
api.start = function(projectId) {
var url = 'foobar/'+projectId;
var d = $.Deferred();
function next() {
setTimeout(function(){
$.get(url).then(function(foo) {
if (foo === 'bar') {
// continue checking
next();
} else {
// done now
d.resolve(foo);
}
}, function(err) {
// error so stop
// don't want to forever loop in an error condition
d.reject(err);
});
}, 2000);
}
next();
return d.promise();
};
return api;
}) (jQuery);
FYI, if you control the server end of things here, it looks like an ideal situation for a webSocket where, rather than polling every two seconds, you can tell the server you want to be notified when things change and the server can just tell you when something changes on the server side.