Different Result from Array.sort() ?

Different Result from Array.sort() on Chrome and Safari with same input ?

Different Result from Array.sort() ?

different-result-from-array-sort-on-chrome-and-safari-with-same-input
Recently come across a weird problem with the sort on Safari and Chrome; they had different result from Array.sort().

As the following code of sorting some numbers even or odd, it will generate different results.

var array1 = [1, 30, 4, 21];
array1.sort(i => i%2 === 0);
console.log(array1);

Following is the output from Chrome and Safari Console:

Array.prototype.sort() on Chrome v69.0.3497.100 
Array.prototype.sort() on Safari v12.0.1 (14606.2.102)

What happened above is that after considering the sort function, Chrome sorted in ascending order while Safari sorted in descending order.

Then I looked up for the Specification at http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.11 and found out that the compare function must return one value from [negative-value, 0, positive-value]

Solution:

Do not rely on boolean output. Return values should be one of negative, 0 or positive.

If you need complex sorting; like from objects, sort the set by cherry picking the needed values.

The use case where I discovered this issue was when for some Hotels we needed to display some include and not-included features but maintain same order in all browsers.

Considering following data:

const features = [
  'rooftop_bar',
  'pool',
];

/*
  Output should be 
    ✅ Pool
    ✅ Rooftop Bar
    ❌ Spa
*/
const features2 = [
  'spa',
  'pool',
  'rooftop_bar',
];

/*
  Output should be 
    ✅ Pool
    ✅ Rooftop Bar
    ✅ Spa
*/

Old Approach:

const getFeatures = (features) => { // Translate and Sort features
  const featuresList = [];
  featureList.push({ label: 'Spa', incuded: features.includes('spa') })
  featureList.push({ label: 'Pool', incuded: features.includes('pool') })
  featureList.push({
    label: 'Rooftop Bar',
    incuded: features.includes('rooftop_bar'),
  });
  return featuresList.sort(f => f.included);
}

New Approach:

const getFeatures = (features) => {  // Translate and Sort features
  // new array to prepare the sort order
  const order = ['pool', 'rooftop_bar', 'spa'];

  const featuresList = {};
  featureList['spa'] = { label: 'Spa', incuded: features.includes('spa') };
  featureList['pool'] = { 
    label: 'Pool',
    incuded: features.includes('pool'),
  };
  featureList['rooftop_bar'] = {
    label: 'Rooftop Bar',
    incuded: features.includes('rooftop_bar'),
  };

  // cherrypick the elements as per the order from translated collection
  return order.map(key => featureList[key]);
}

Small drawback is that you need to know the order of the keys beforehand which conflicts with the dynamic nature of UI and might introduce errors based on some used cases.

But as the UX is considered, showing same order of features on different browser can make you choose to safe guard the possible errors rather than showing random ordering of features.


Updates

#1

Chrome v71.0.3578.98 is also behaving is weird way as it does no sort operation on the single parameter sorter function.

Chrome v71.0.3578.98 (Official Build) (64-bit)

Though in Safari, same operation results in different output:

Safari v12.0.3 (14606.4.2)

Thought the reason of these different behaviors can be the Algorithm doing this operations. As per MDN page, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort; the sort id done by converting the values to String and then replacing the values.

And from above screenshots; Safari tends to work fine when used with function notations, but not with arrow functions

And Chrome tends to provide more fucked-up results.


Let me know your thoughts and opinions about this article through comments ? or on twitter at @heypankaj_ and @time2hack.

If you find this article useful, share it with others ?