Showing posts with label javascript. Show all posts
Showing posts with label javascript. Show all posts

Tuesday, November 27, 2018

await in turn by mistake

I really enjoy working with the new async functions but it is really easy to set up a situation where code that could be running in parallel is force to run in sequence. Consider this simple invocation of a javascript function that takes two values returned from other async functions:

   const combination = await combine(await value(1), await value(2));

The problem here is that unless the JS environment does some optimisation for you, the actions contained in the two called to await are performed in series.

function action() {
   return new Promise(resolve => {
       setTimeout(resolve, 5000)
   })
}

async function value(v) {
   console.log('started ' + v);
   return action('broken ' + v).then(()=> {
      console.log("finished " + v);
      return "ok "  + v
   })
}

async function test() {
 console.log(await value('1') + " " + await value('2'));
}
test();

This will result in an output that look something like this, and will take about ten seconds:

(index):39 started 1
(index):39 finished 1
(index):37 started 2
(index):39 finished 2
(index):44 ok 1 ok 2

So there are a number of way to re-write this code in order to ensure that the original processes are all started at the same time, I think the second is the best form, just wish they had put some syntactic sugar so we could do away with Promise.all references.

async function test() {
  // Worst
  const three = value('3');
  const four = value('4');
  console.log(await three + " " + await four);

  // Better
  const [five, six] = await Promise.all([value('5'), value('6')]; 
  console.log(five + " " + six));

  // Better?
  console.log(...await Promise.all([value('7'), value('8')]))
}
test();

So you would expect a test output to be similar to this, with each block taking about the minimum 5 seconds.

(index):37 started 3
(index):37 started 4
(index):39 finished 3
(index):39 finished 4
(index):47 ok 3 ok 4
(index):37 started 5
(index):37 started 6
(index):39 finished 5
(index):39 finished 6
(index):48 ok 5 ok 6
(index):37 started 7
(index):37 started 8
(index):39 finished 7
(index):39 finished 8
(index):48 ok 7 ok 8

It is really easy to make the same mistake when writing a for loop for example by waiting on each item in turn

async function test() {

  const list = ['1', '2', '3'];
  const result = [];
  for (const item of list) {
     result.push(await value(item));
  }

  console.log(result);
}

For most operation that involve Promise and loop you will normally need map/Promise.all at some point. This version should complete in around the minimum 5 seconds.

async function test() {

  const list = ['1', '2', '3'];
  const result = await Promise.all(list.map(value));

  console.log(result);
}

Friday, October 5, 2018

Understanding ordering with JavaScript async methods

So I challenged someone in a code review to prove that there code that made use of async functions wouldn't be susceptible to a race condition. To that end I came up with a very trivial code example to demonstrate the issue, worth trying to write down the output and line orderings before you read on.

let list;

async function clearList () {
    list = []; // A
}

async function processList (processList) {
   await clearList(); // B
   list = list.concat(processList); // C
}

processList([1,2,3]); // D
processList([4,5,6]) // E
   .then(() => {
      console.dir(list); // F
   }) 

So the two questions here are what is the output of this code and what order do the lines of code get executed in. Now the point of the code was that it would show that the output is [1,2,3,4,5,6] because of the race condition; but the actual ordering of the execution of the lines of code I had wrong.

My assumption was that you would not enter the async method directly and instead be queued to be performed on a later clock tick. This gave me an execution flow of D,E,B,B,A,A,C,C,F which I was happy with until my coworker Millan Kuchtiak pointed out I was entirely incorrectly.

It turns out that at least in Chrome and Safari that code doesn't get transferred queue until you reach the first await call. So actually when you run the code through with a debugger the flow is D,B,A,E,B,A,C,C,F. This make sense from a performance point of view, some async methods might never need the change of context, so just in time async.

To summarise an async method is synchronous up until the first await

Tuesday, March 14, 2017

The "debugger" reserved word in JavaScript

This is a handy little tip for cases where you just can't get your debugger to start in the right context or the framework you are using doesn't give you a clear path to know when you file might be loaded. (For example ABCS, or Application Builder Cloud Service, that I am currently working on has this problem)

In this case just add the debugger statement in your javascript and voila when run in a browser the debugger is popped up.

   someCode().then(value => {
       debugger;
   });


Always pays to read the reserved word list when learning a new language, you never know what you might find.

Wednesday, January 20, 2016

Removing multiple items from a JavaScript array, no for loops

So I was reviewing some code today that was using two for loops to remove elements from a JavaScript array, I won't relate the code here but I figured there as got to be a nicer way. So the first guess is to make use of the filter method; but that creates a new array which might be problem if the array is large but not if it is fairly small or not called very often.

providers = providers.filter(function(provider) {
     return provider.age > 35
}

For performance or API reasons you might want to perform an in place removal using splice, so you can simple map the values to indexes you want to remove, reverse the order then perform a sequence of splice operations:

providers.map(function(provider, index) {
    return provider.age > 35 ? index : -1;
}).filter(function(index) {
    return index >= 0;
}).reverse().forEach(function(index) {
    providers.splice(index,1);
});

You can play with this code in this jsfiddle.

Update 21 Jan 20125: The hazard of doing full stack development is that it is easy to accidentally duck type between languages whilst forgetting the impact. In Java the equivalent streaming code using map just returns another lazy step in the stream whereas in JavaScript you end up creating a new array of the same size as the original. This removes the performance improvement over the first example if you have lots of data. This is simple too fix though as you can use the reduce function to just create an array and populate it using the reduce operation:

providers.reduce(function(list, provider, index) {
    if (provider.age> 35) list.push(index);
    return list;
}, []).reverse().forEach(function(index) {
    providers.splice(index,1);
});

Here is the updated fiddle. I guess at some point I am going to have to benchmark these variants to see which is best; but that is for a less busy day.

Thursday, February 12, 2015

How long did I sleep last night: using Cordova, HealthKit, and JavaScript, and a handful of Promises

Now that we can know how much activity I did yesterday, we can look at whether I am getting enough sleep.

A quick look at the Apple documentation lets us see that HKCategoryTypeIdentifierSleepAnalysis is the right measure to be using. This is a subtype of SampleType so we can use the querySampleType function.

First of all here is the same boilerplate code, with the minor modification to allow for access to the sleep data rather than the step data we used before. You could of course combine the two.

var wearable = {

    avaliable: _.once(function() {

        return new Promise(function(resolve, reject) {
        
            if (window.plugins.healthkit) {
                window.plugins.healthkit.available(
                        function() {
                            console.log("Healthkit is avaliable");
                            resolve(true);
                        },
                        function() {
                            console.log("Healthkit is not avaliable");
                            reject(Error(false));
                        }
                );
            } else {
                reject(Error("HealthKit Not Available"));
            }
        });
    }),


    getHealthKit : _.once(function() {

        return wearable.avaliable().then(function() {

            return new Promise(function(resolve, reject) {
                window.plugins.healthkit.requestAuthorization(
                        {
                            'readTypes': ['HKCategoryTypeIdentifierSleepAnalysis'],
                            'writeTypes': []
                        },
                function() {
                    console.log("HealthKit authorisation accepted");
                    resolve(window.plugins.healthkit);
                },
                function() {
                    reject(Error("HealthKit authorisation rejected"));
                });
            });
        });
    }),

    ...

}



Then it is s simple matter of querying HeathKit with a useful date range. Note I use limit and ascending to access the last data point - it is possible that different tools will report multiple entires for a single night. (In particular new parents) I need to do a little bit more investigation here.

wearable = {

    ... 

    querySleep: function() {

        wearable.getHealthKit().then(function(healthkit) {

                var startDate = moment().subtract('d', 1).toDate();

                healthkit.querySampleType({
                        'startDate': startDate,
                        'endDate': moment().toDate(),
                        'sampleType': "HKCategoryTypeIdentifierSleepAnalysis",
                        'ascending': "NO",
                        'limit': 1
                    },
                    function(value) {
                        console.log("Success for runing sleep query");

                        // Debug output for the momment
                        value.forEach(function(next) {

                            console.dir(next);
                            var measure = next.value === 1 ? "Sleeping" : "In Bed";
                            var minutesSleep = moment(next.endDate).diff(next.startDate, "minutes");
                            var hoursSleep = moment(next.endDate).diff(next.startDate, "hours");
                            console.log("Entry got " + minutesSleep + " minutes " + measure);
                            console.log("Entry got " + hoursSleep + " hours " + measure);

                        });

                        // Use data in some way
                    });

            }
        }
    },
}


Finally it would be really useful to be able to be told when data is added to HealthKit by another app for example the client app for a wearable. You can do this using the monitorSampleType function. Ideally this would code would then use something called an anchored query so you only access the most recently added data; but I haven't had time to implement this yet so we simply call querySleep().

Now making sure you app wakes up in the background to receive this data is a whole other blog....

var wearable = {

    ...

    monitorSleep: _.once(function() {

        console.log("Starting to monitor sleep");


        wearable.getHealthKit().then(function(healthkit) {
            healthkit.monitorSampleType({
                    'sampleType': "HKCategoryTypeIdentifierSleepAnalysis"
                },
                _.debounce(function(value) {
                    console.log("Sleep data has been updated, lets see if anything interesting has been added");
                    wearable.querySleep();
                }, 2000),
                function() {
                    console.log("Failed to monitor sample data");
                    console.dir(arguments);
                });

        });
    })
}




Sunday, February 1, 2015

How many steps did I walk yesterday: using Cordova, HealthKit, and JavaScript, and a handful of Promises

As people who know me might know I have been playing around with various wearables for some years now, starting with a Nike Fuel Band, and also trying out products from Misfit and UP along the way. I have mostly just watched with interest as my exercise levels, and indeed sleep now we have a little one in the house, has gone up and down over time.

More recently with the arrival of HealthKit there comes a standard method of accessing this information from multiple products on the iPhone, I needed to brush up on my JavaScript for work so I decided to have a play in Cordova to build something in my spare time. One of the cool things about that Cordova community when compared to other app development frameworks is the large and healthy plugin community - and quick google and I found what I needed to support HealthKit HealthKit. This took only a small amount of patching from me to get at the information I wanted. In this blog I will just look at number of steps per day as a proxy for activity.

I am going to assume the rest of the Cordova App is in place and start with a simple wearable object which exposes a promise that allows you to check whether HealthKit is available on this platform, you might have iOS 8 but HealthKit is iPhone only. Remember that most of Cordova is interacting with a bunch of asynchronous native systems so you have to make sure you consider this as you work with it. Hence the liberal use of promises. (If you are wanting to support < iOS 8 then you will need to use a different promise library / shim) In this case I am also using the underscorejs library to create a promise who's result won't be calculated until it is invoked for the first time.

var wearable = {

    avaliable: _.once(function() {

        return new Promise(function(resolve, reject) {
        
            if (window.plugins.healthkit) {
                window.plugins.healthkit.available(
                        function() {
                            console.log("Healthkit is avaliable");
                            resolve(true);
                        },
                        function() {
                            console.log("Healthkit is not avaliable");
                            reject(Error(false));
                        }
                );
            } else {
                reject(Error("HealthKit Not Available"));
            }
        });
    }),

...

}

The next stage of the code, once you are sure that the system is available, is to get verify that you can access the data you want to access. Now Apple is clear in there developer guide that you should only request the data you might want to access once. So no asking for step data one time then heath rate data in a different part of the UI. The Cordova HealthKit Plugin will just return with a success if those values are already authorised, so we will use another promise as a gateway to actually working with the data to make one consistent entry point for authorisation.

var wearable = {

...


    getHealthKit : _.once(function() {

        return wearable.avaliable().then(function() {

            return new Promise(function(resolve, reject) {
                window.plugins.healthkit.requestAuthorization(
                        {
                            'readTypes': ['HKQuantityTypeIdentifierStepCount'],
                            'writeTypes': []
                        },
                function() {
                    console.log("HealthKit authorisation accepted");
                    resolve(window.plugins.healthkit);
                },
                function() {
                    reject(Error("HealthKit authorisation rejected"));
                });
            });
        });
    }),

...

}

Finally now we have all the ground work in place, lets put in place a simple method to work out how many steps were recorded yesterday. I am then using special function called sumQuantityType that performs some rather clever statistical leg work for you. Consider if you have a iPhone 6 along with step monitoring wrist band of some kind like a Apple Watch, in HealthKit you will have two lots of data that inconsistently overlaps. HealthKit provides methods to deal with this, more information can be found in the WWDC presentation from 2014, but basically it works out for each time segment which data source(s) to use to get the value for the time range requested.

var wearable = {

...


    querySteps : function() {
        
        return wearable.getHealthKit().then(function (healthkit)
        {
            return new Promise(function(resolve, reject) {

                var m = moment().startOf('day');
                var endDate = m.toDate();
                var startDate = moment(m).subtract('d', 1).toDate();

                healthkit.sumQuantityType({
                        'startDate': startDate,
                        'endDate': endDate,
                        'sampleType': "HKQuantityTypeIdentifierStepCount"
                    },
                    function(value)
                    {
                        resolve(value)
                    },
                    function()
                    {
                        reject(Error("Problem queuing steps"));
                    });
        });
    }

}

Now we have this object, we can access the data and show it to the user. Note that the promise objects chain so the catch function will tell you if something went wrong at any stage of the process, if you had tried to wire this up with event handlers you would have had an ungodly mess of code and callbacks.

wearable.querySteps()
       .then(function(value){
           console.log("Number of steps "  + value);
           ... update UI
       })
       .catch(function (error) {
           ... update UI
       };


This is of course just the start, there is a lot more interesting stuff you can get from this API; but that is for another day. I like HealthKit because unlike GoogleFit all your health data is stored locally by default. There is a lot of potential here for doing interesting stuff.

Finally a quick shout out of Eddy Verbruggen who created HealthKit plugin along with a whole host of other interesting libraries.

I am going to walk around for a bit now, gotta meet those targets. :-)