code: async function map(array, fn, self = undefined) {
    const v = await Promise.resolve(array);
    if (!Array.isArray(v)) {
      throw TypeError('not an array');
    }
  
    const arr = /** @type {!Array} */(v);
    const n = arr.length;
    const values = new Array(n);
  
    for (let i = 0; i < n; i++) {
      if (i in arr) {
        values[i] = await Promise.resolve(fn.call(self, arr[i], i, arr));
      }
    }
    return values;
  }
docs: Calls a function for each element in an array and inserts the result into a
  new array, which is used as the fulfillment value of the promise returned
  by this function.
  
  If the return value of the mapping function is a promise, this function
  will wait for it to be fulfilled before inserting it into the new array.
  
  If the mapping function throws or returns a rejected promise, the
  promise returned by this function will be rejected with the same reason.
  Only the first failure will be reported; all subsequent errors will be
  silently ignored.
  
  @param {!(Array<TYPE>|IThenable<!Array<TYPE>>)} array The array to iterate
  over, or a promise that will resolve to said array.
  @param {function(this: SELF, TYPE, number, !Array<TYPE>): ?} fn The
  function to call for each element in the array. This function should
  expect three arguments (the element, the index, and the array itself.
  @param {SELF=} self The object to be used as the value of 'this' within `fn`.
  @template TYPE, SELF