Alternative or polyfill for Array.from on the Internet Explorer
Asked Answered
K

6

43

I have a problem with my Angular App on the Internet Explorer. It runs everywhere without a problem (Chrome, Mozilla, Edge), but NOT on the IE.

I have analyzed with the Developer Explorer where the error is and it returned that the error occurs on the following line:

myDataSet[index - 1].data = Array.from(tmp);

Where this is the following error message I am getting:

Object does not support property or method from at Anonymous function....(etc.)

What I am doing there is that I have a Set() named tmp which contains the following data:

enter image description here

Afterwards I am simply creating a simple array object from this Set.

How can I solve this problem?

EDIT

Based on the recommendations I have added the following code to my App:

if (!Array.from) {
  Array.from = (function () {
    var toStr = Object.prototype.toString;
    var isCallable = function (fn) {
      return typeof fn === 'function' || toStr.call(fn) === '[object Function]';
    };
    var toInteger = function (value) {
      var number = Number(value);
      if (isNaN(number)) { return 0; }
      if (number === 0 || !isFinite(number)) { return number; }
      return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
    };
    var maxSafeInteger = Math.pow(2, 53) - 1;
    var toLength = function (value) {
      var len = toInteger(value);
      return Math.min(Math.max(len, 0), maxSafeInteger);
    };

    // The length property of the from method is 1.
    return function from(arrayLike/*, mapFn, thisArg */) {
      // 1. Let C be the this value.
      var C = this;

      // 2. Let items be ToObject(arrayLike).
      var items = Object(arrayLike);

      // 3. ReturnIfAbrupt(items).
      if (arrayLike == null) {
        throw new TypeError("Array.from requires an array-like object - not null or undefined");
      }

      // 4. If mapfn is undefined, then let mapping be false.
      var mapFn = arguments.length > 1 ? arguments[1] : void undefined;
      var T;
      if (typeof mapFn !== 'undefined') {
        // 5. else
        // 5. a If IsCallable(mapfn) is false, throw a TypeError exception.
        if (!isCallable(mapFn)) {
          throw new TypeError('Array.from: when provided, the second argument must be a function');
        }

        // 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined.
        if (arguments.length > 2) {
          T = arguments[2];
        }
      }

      // 10. Let lenValue be Get(items, "length").
      // 11. Let len be ToLength(lenValue).
      var len = toLength(items.length);

      // 13. If IsConstructor(C) is true, then
      // 13. a. Let A be the result of calling the [[Construct]] internal method of C with an argument list containing the single item len.
      // 14. a. Else, Let A be ArrayCreate(len).
      var A = isCallable(C) ? Object(new C(len)) : new Array(len);

      // 16. Let k be 0.
      var k = 0;
      // 17. Repeat, while k < len… (also steps a - h)
      var kValue;
      while (k < len) {
        kValue = items[k];
        if (mapFn) {
          A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k);
        } else {
          A[k] = kValue;
        }
        k += 1;
      }
      // 18. Let putStatus be Put(A, "length", len, true).
      A.length = len;
      // 20. Return A.
      return A;
    };
  }());
}
Kisor answered 23/4, 2016 at 12:40 Comment(0)
A
5

The first CORRECT answer here and for MDN

Original poster of question wrote:

What I am doing there is that I have a Set() named tmp ...

But we have here 3 answers for already more than 4 years and nobody has tested it with a Set object.
The huge polyfill from MDN does not work with Set object!

It was copied from MDN and pasted into the accepted answer without testing.

My polyfill solution is also much shorter than incorrect and huge polyfill from MDN.

In the following solution you will find the explanation about the function(s) and their parameters in comments.

/**
 * @param "arr" (required) - array-like or iterable object to convert it to an array.
 * @param "callbackFn" (optional) - function to call on every element of the array.
 * @param "thisArg" (optional) - value to use as this when executing callback
 * Return value - new Array instance
 *
 * The callbackFn argument usage is like in Array.map() callback.
 * The callbackFn function accepts the following arguments:
 *      @param "currentValue" (required) - the current element being processed in the array.
 *      @param "index" (optional) - the index of the current element being processed in the array.
 *      @param "array" (optional) - he array map was called upon.
 * Callback function that is called for every element of "arr". Each time callback executes, the returned value is added to new array ("arNew").
 */
function arrayFrom(arr, callbackFn, thisArg)
{
    //if you need you can uncomment the following line
    //if(!arr || typeof arr == 'function')throw new Error('This function requires an array-like object - not null, undefined or a function');

    var arNew = [],
        k = [], // used for convert Set to an Array
        i = 0;

    //if you do not need a Set object support then
    //you can comment or delete the following if statement
    if(window.Set && arr instanceof Set)
    {
        //we use forEach from Set object
        arr.forEach(function(v){k.push(v)});
        arr = k
    }

    for(; i < arr.length; i++)
        arNew[i] = callbackFn
            ? callbackFn.call(thisArg, arr[i], i, arr)
            : arr[i];

    return arNew
}

//You could also use it without the following line, but it is not recommended because native function is faster.
Array.from = Array.from || arrayFrom; //We set it as polyfill

//HOW TO USE IT:

function myCallback1(x){return x+x}

function myCallback2(o){return o.innerHTML}

var str = 'Super!',
    array = str.split(''),//['S','u','p','e','r','!']
    arrayLike1 = window.Set ? new Set(str) : array, //array for IE < 10. Only 11 version of IE supports Set.
    arrayLike2 = document.querySelectorAll('b');//NodeList
    arrayLike3 = document.getElementsByTagName('b');//HTMLCollection

console.log(arrayFrom(str).join(','));//S,u,p,e,r,!
console.log(arrayFrom(array).join(','));//S,u,p,e,r,!
console.log(arrayFrom(str, myCallback1).join(','));//SS,uu,pp,ee,rr,!!
console.log(arrayFrom(arrayLike1, myCallback1).join(','));//SS,uu,pp,ee,rr,!!
console.log(arrayFrom(arrayLike2, myCallback2).join(','));//aaa,bbb
console.log(arrayFrom(arrayLike3, myCallback2).join(','));//aaa,bbb
//You can also use it as polyfill:
console.log(Array.from(str).join(','));//S,u,p,e,r,!
<b>aaa</b> <b>bbb</b>

Do not forget that a Set has unique values! For example:

//Only 11 version of IE and all modern browsers support Set.
var ar = [];
new Set('mama').forEach(function(v){ar.push(v)});
console.log(ar.join(',')); // 'm','a'
Anthem answered 1/7, 2020 at 17:43 Comment(0)
A
58

Array.from not supported in the following document modes: Quirks, Internet Explorer 6 standards, Internet Explorer 7 standards, Internet Explorer 8 standards, Internet Explorer 9 standards, Internet Explorer 10 standards, Internet Explorer 11 standards. Not supported in Windows 8.1 (compatibility reference)

Just add the code below to your page (JS code was copied from developer.mozilla.org). It will emulate an ES6's Array.from method.

Array.from was added to the ECMA-262 standard in the 6th edition; as such it may not be present in other implementations of the standard. You can work around this by inserting the following code at the beginning of your scripts, allowing use of Array.from in implementations that don't natively support it. This algorithm is exactly the one specified in ECMA-262, 6th edition, assuming Object and TypeError have their original values and that callback.call evaluates to the original value of Function.prototype.call. In addition, since true iterables can not be polyfilled, this implementation does not support generic iterables as defined in the 6th edition of ECMA-262.

if (!Array.from) {
  Array.from = (function () {
    var toStr = Object.prototype.toString;
    var isCallable = function (fn) {
      return typeof fn === 'function' || toStr.call(fn) === '[object Function]';
    };
    var toInteger = function (value) {
      var number = Number(value);
      if (isNaN(number)) { return 0; }
      if (number === 0 || !isFinite(number)) { return number; }
      return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
    };
    var maxSafeInteger = Math.pow(2, 53) - 1;
    var toLength = function (value) {
      var len = toInteger(value);
      return Math.min(Math.max(len, 0), maxSafeInteger);
    };

    // The length property of the from method is 1.
    return function from(arrayLike/*, mapFn, thisArg */) {
      // 1. Let C be the this value.
      var C = this;

      // 2. Let items be ToObject(arrayLike).
      var items = Object(arrayLike);

      // 3. ReturnIfAbrupt(items).
      if (arrayLike == null) {
        throw new TypeError("Array.from requires an array-like object - not null or undefined");
      }

      // 4. If mapfn is undefined, then let mapping be false.
      var mapFn = arguments.length > 1 ? arguments[1] : void undefined;
      var T;
      if (typeof mapFn !== 'undefined') {
        // 5. else
        // 5. a If IsCallable(mapfn) is false, throw a TypeError exception.
        if (!isCallable(mapFn)) {
          throw new TypeError('Array.from: when provided, the second argument must be a function');
        }

        // 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined.
        if (arguments.length > 2) {
          T = arguments[2];
        }
      }

      // 10. Let lenValue be Get(items, "length").
      // 11. Let len be ToLength(lenValue).
      var len = toLength(items.length);

      // 13. If IsConstructor(C) is true, then
      // 13. a. Let A be the result of calling the [[Construct]] internal method of C with an argument list containing the single item len.
      // 14. a. Else, Let A be ArrayCreate(len).
      var A = isCallable(C) ? Object(new C(len)) : new Array(len);

      // 16. Let k be 0.
      var k = 0;
      // 17. Repeat, while k < len… (also steps a - h)
      var kValue;
      while (k < len) {
        kValue = items[k];
        if (mapFn) {
          A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k);
        } else {
          A[k] = kValue;
        }
        k += 1;
      }
      // 18. Let putStatus be Put(A, "length", len, true).
      A.length = len;
      // 20. Return A.
      return A;
    };
  }());
}
Armagnac answered 23/4, 2016 at 12:42 Comment(8)
thank you for explaining this. Could you advise, what code to use to replace my Array.from, please?Kisor
I have updated my answer. Just copy JavaScript code into your page. For example to the main JS file. And your problem will be solved!Armagnac
You may try myDataSet[index - 1].data = Array.prototype.slice.call(tmp);Meadowlark
I have tried to use your line but it did not work on my end. When using Array.From I have noticed that the function has issues when I am passing a Set to it (e.g. id does not recognize lenght of the array, mapFn is undefined, ...)Kisor
@Robert J. if tmp object does not have a length property first try inserting a length property to it and give it a correct value. If it doesn't work you have to make the tmp object iterable by inserting a [Symbol.iterator] method. (study this developer.mozilla.org/tr/docs/Web/JavaScript/Reference/…) once tmp is an iterable object then you can safely use myDataSet[index - 1].data = Array.prototype.slice.call(tmp); method. Could you please list the tmp object in the question.Meadowlark
this is not solving the problem in IE at all.Lorindalorine
@RichardZilahi, you can see 30+ upvotes here. I think that solution solves problem in IE. Maybe you have a local problem with your code. Is there any error in IE console?Armagnac
I am getting error after adding your code and checked in Internet Explorer 11. This site overrides Array.from() with an implementation that doesn't support iterables, which could cause Google Maps JavaScript API v3 to not work correctly.Falkirk
M
14

I faced the same issue. Looked at the polyfill and it is threatening huge. Here is 2 lines short solution.

The OP basically needs to create simple array from his array-like object. I used to my taste the most efficient 2 lines plain for loop (I had to make array from HTML DOM nodelist array-like object, same applicable to JavaScript arguments object).

For the OP's case it could sound this way:

var temp_array = [],
    length = tmp.length;

for (var i = 0; i < length; i++) {
    temp_array.push(tmp[i]);
}

// Here you get the normal array "temp_array" containing all items
// from your `tmp` Set.

Make it separate function and you get 3 lines universal reusable solution for the IE<9 case.

Here is how the separate function may look like:

/**
 * @param arr The array | array-like data structure.
 * @param callback The function to process each element in the 'arr'.
 * The callback signature and usage is assumed similar to the 
 * native JS 'forEach' callback argument usage.
 */
function customEach(arr, callback) {
    'use strict';
    var l = arr.length;
    for (var i = 0; i < l; i++) {
        callback(arr[i], i, arr);
    }
};

PS: here is the relevant description for forEach callback to see how to use the customEach callback.

Monad answered 21/12, 2016 at 11:17 Comment(0)
A
7

While it's not supported on IE, you may use the polyfill from MDN.

Anse answered 23/4, 2016 at 12:43 Comment(0)
P
7

You could use slice.call for array-like objects. This means your code would read:

myDataSet[index - 1].data = [].slice.call(tmp);

Pantagruel answered 15/9, 2020 at 5:56 Comment(1)
one-liner that works like a charm. works even in IEHighminded
A
5

The first CORRECT answer here and for MDN

Original poster of question wrote:

What I am doing there is that I have a Set() named tmp ...

But we have here 3 answers for already more than 4 years and nobody has tested it with a Set object.
The huge polyfill from MDN does not work with Set object!

It was copied from MDN and pasted into the accepted answer without testing.

My polyfill solution is also much shorter than incorrect and huge polyfill from MDN.

In the following solution you will find the explanation about the function(s) and their parameters in comments.

/**
 * @param "arr" (required) - array-like or iterable object to convert it to an array.
 * @param "callbackFn" (optional) - function to call on every element of the array.
 * @param "thisArg" (optional) - value to use as this when executing callback
 * Return value - new Array instance
 *
 * The callbackFn argument usage is like in Array.map() callback.
 * The callbackFn function accepts the following arguments:
 *      @param "currentValue" (required) - the current element being processed in the array.
 *      @param "index" (optional) - the index of the current element being processed in the array.
 *      @param "array" (optional) - he array map was called upon.
 * Callback function that is called for every element of "arr". Each time callback executes, the returned value is added to new array ("arNew").
 */
function arrayFrom(arr, callbackFn, thisArg)
{
    //if you need you can uncomment the following line
    //if(!arr || typeof arr == 'function')throw new Error('This function requires an array-like object - not null, undefined or a function');

    var arNew = [],
        k = [], // used for convert Set to an Array
        i = 0;

    //if you do not need a Set object support then
    //you can comment or delete the following if statement
    if(window.Set && arr instanceof Set)
    {
        //we use forEach from Set object
        arr.forEach(function(v){k.push(v)});
        arr = k
    }

    for(; i < arr.length; i++)
        arNew[i] = callbackFn
            ? callbackFn.call(thisArg, arr[i], i, arr)
            : arr[i];

    return arNew
}

//You could also use it without the following line, but it is not recommended because native function is faster.
Array.from = Array.from || arrayFrom; //We set it as polyfill

//HOW TO USE IT:

function myCallback1(x){return x+x}

function myCallback2(o){return o.innerHTML}

var str = 'Super!',
    array = str.split(''),//['S','u','p','e','r','!']
    arrayLike1 = window.Set ? new Set(str) : array, //array for IE < 10. Only 11 version of IE supports Set.
    arrayLike2 = document.querySelectorAll('b');//NodeList
    arrayLike3 = document.getElementsByTagName('b');//HTMLCollection

console.log(arrayFrom(str).join(','));//S,u,p,e,r,!
console.log(arrayFrom(array).join(','));//S,u,p,e,r,!
console.log(arrayFrom(str, myCallback1).join(','));//SS,uu,pp,ee,rr,!!
console.log(arrayFrom(arrayLike1, myCallback1).join(','));//SS,uu,pp,ee,rr,!!
console.log(arrayFrom(arrayLike2, myCallback2).join(','));//aaa,bbb
console.log(arrayFrom(arrayLike3, myCallback2).join(','));//aaa,bbb
//You can also use it as polyfill:
console.log(Array.from(str).join(','));//S,u,p,e,r,!
<b>aaa</b> <b>bbb</b>

Do not forget that a Set has unique values! For example:

//Only 11 version of IE and all modern browsers support Set.
var ar = [];
new Set('mama').forEach(function(v){ar.push(v)});
console.log(ar.join(',')); // 'm','a'
Anthem answered 1/7, 2020 at 17:43 Comment(0)
T
1

While it is not a full polyfill, the following has worked for all my use cases so far:

Array.from||(Array.from=function(ar){return Array.prototype.slice.call(ar)});

If found this years ago on MDN, but they have removed it since.

Tangency answered 25/8, 2022 at 14:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.