简体   繁体   中英

Why does JavaScript map function return undefined?

My code

 var arr = ['a','b',1];
 var results = arr.map(function(item){
                if(typeof item ==='string'){return item;}  
               });

This gives the following results

["a","b",undefined]

I don't want undefined in the results array. How can I do it?

You aren't returning anything in the case that the item is not a string. In that case, the function returns undefined , what you are seeing in the result.

The map function is used to map one value to another, but it looks like you actually want to filter the array, which a map function is not suitable for.

What you actually want is a filter function. It takes a function that returns true or false based on whether you want the item in the resulting array or not.

var arr = ['a','b',1];
var results = arr.filter(function(item){
    return typeof item ==='string';  
});

Filter works for this specific case where the items are not modified. But in many cases when you use map you want to make some modification to the items passed.

if that is your intent, you can use reduce :

var arr = ['a','b',1];
var results = arr.reduce((results, item) => {
    if (typeof item === 'string') results.push(modify(item)) // modify is a fictitious function that would apply some change to the items in the array
    return results
}, [])

Since ES6 filter supports pointy arrow notation (like LINQ):

So it can be boiled down to following one-liner.

['a','b',1].filter(item => typeof item ==='string');

My solution would be to use filter after the map.

This should support every JS data type.

example:

const notUndefined = anyValue => typeof anyValue !== 'undefined'    
const noUndefinedList = someList
          .map(// mapping condition)
          .filter(notUndefined); // by doing this, 
                      //you can ensure what's returned is not undefined

You can implement like a below logic. Suppose you want an array of values.

let test = [ {name:'test',lastname:'kumar',age:30},
             {name:'test',lastname:'kumar',age:30},
             {name:'test3',lastname:'kumar',age:47},
             {name:'test',lastname:'kumar',age:28},
             {name:'test4',lastname:'kumar',age:30},
             {name:'test',lastname:'kumar',age:29}]

let result1 = test.map(element => 
              { 
                 if (element.age === 30) 
                 {
                    return element.lastname;
                 }
              }).filter(notUndefined => notUndefined !== undefined);

output : ['kumar','kumar','kumar']

You only return a value if the current element is a string . Perhaps assigning an empty string otherwise will suffice:

var arr = ['a','b',1];
var results = arr.map(function(item){
    return (typeof item ==='string') ? item : '';  
});

Of course, if you want to filter any non-string elements, you shouldn't use map() . Rather, you should look into using the filter() function.

var arr = ['a','b',1];
 var results = arr.filter(function(item){
                if(typeof item ==='string'){return item;}  
               });

If you have to use map to return custom output, you can still combine it with filter.

const arr = ['a','b',1]

const result = arr.map(element => {

  if(typeof element === 'string')
    return element + ' something'

}).filter(Boolean) // this will filter out null and undefined

console.log(result) // output: ['a something', 'b something']

If you use it like this, your problem will be solved. Also, you will have a clean and short code

var _ = require('lodash'); //but first, npm i lodash --save
var arr = ['a','b',1];
var results = _.compact(
    _.map(arr, function(item){
        if(_.isString(item)){return item;}
    }
); //false, null, undefined ... etc will not be included

with ES6...

const _ = require('lodash'); //but first, npm i lodash --save
const arr = ['a','b',1];
const results = _.compact(
    _.map(arr, item => {
        if(_.isString(item)){return item;}
    }
);

I run into this quite frequently where the type after filtering will still be string | number string | number . So, to expand upon these solutions and include type safety you can use a user-defined type guard. https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates

const result = ['a','b',1].filter((item) => typeof item ==='string');
// result is typed as (string | number)[]

Better type safety using user-defined type guard

const result = ['a','b',1].filter((item): item is string => typeof item ==='string');
// result is now typed as string[]

The problem: the issue is arr.map() will do a full iteration of arr array length, ie map() method will loop as much as the length of arr is, no matter what condition you have inside it, so if you defined a condition inside it eg if(typeof item ==='string'){return item;} even if the condition is not happening, the map() will be forced to keep looping until finishing the looping of the whole arr so it will give you undefined for the rest of elements if the condition is not met.

The solutions:

Solution One: if you want to return the whole item in the array when the condition is met, you can use arr.filter() so the filter will return the whole item for the iteration eg if you have array of objects like bellow

const arr = [{name: "Name1", age: 25}, {name: "Name2", age: 30}, {name: "Name3", age: 25}]

and you want to return the whole objects when the condition is met like example below

const filteredItems = arr.filter((item)=>{if(item.age === 25){return true}})

console.log(filteredItems) //results: [{name: "Name1", age: 25}, {name: "Name3", age: 25}]

conclusion: filter() method returns an array of the whole items in it if the condition is met.

Solution Two: if you want to return only a specific data of the objects (or the whole object or any shape of data) in array ie if you want to return only the names in array without the ages, you can do this

const namesOnly = arr.map((item)=>{if(item.age === 25){return item.name}})

console.log(namesOnly) //results: ["Name1, udefined, "Name3"]

now to remove the undefined you just use filter() method on the results like below

const namesOnly = arr.map((item)=>{if(item.age === 25){return item.name}}).filter((item)=> !!item)

console.log(namesOnly) //results: ["Name1, "Name3"]

conclusion: map() method returns an array of specifically defined data in the return, and returns undefined if the condition is not met. so then we can use filter() method to remove the undefined .

You can filter records with .map easily using below example code

const datapoints = [
    {
        PL_STATUS: 'Packetloss',
        inner_outerx: 'INNER',
        KPI_PL: '97.9619'
    },
    {
        PL_STATUS: 'Packetloss',
        inner_outerx: 'OUTER',
        KPI_PL: '98.4621',
    },
    {
        PL_STATUS: 'Packetloss',
        inner_outerx: 'INNER',
        KPI_PL: '97.8770',
    },
    {
        PL_STATUS: 'Packetloss',
        inner_outerx: 'OUTER',
        KPI_PL: '97.5674',

    },
    {
        PL_STATUS: 'Packetloss',
        inner_outerx: 'INNER',
        KPI_PL: '98.7150',
    },
    {
        PL_STATUS: 'Packetloss',
        inner_outerx: 'OUTER',
        KPI_PL: '98.8969'
    }
];
const kpi_packetloss_inner: string[] = [];
datapoints.map((item: { PL_STATUS: string; inner_outerx: string; KPI_PL: string }) => {
    if (item.PL_STATUS === 'Packetloss' && item.inner_outerx === 'INNER') {
        kpi_packetloss_inner.push(item.KPI_PL);
    }
})
console.log(kpi_packetloss_inner);

Map is used when you want to produced new modified array from the original array. the simple answer may be for someone

      var arr = ['a','b',1];
      var results = arr.filter(function(item){
      // Modify your original array here
     return typeof item ==='string';  
     }).filter(a => a);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM