Why does `arr.find()` return `undefined`? - javascript

In the below code his._notifications is an array of objects, each object contains the properties shown below.
What I am trying to do is to check if each object not passed to the function func() was pushed into the array or not?
So I am using .find() and I expect it returns true if the object was passed to the function or if it already exists in the array, and false otherwise.
But the below log statement prints undefined! Why isExists is undefined? And what is the recommended way to check if an item is duplicate or not in an array?
code:
func(noti)
const isExists = this._notifications.find((obj) =>
obj.title === noti.safeTitle
&& obj.text === noti.safeText
&& obj.bannerTitle === noti.safeBannerTitle
&& obj.bannerText === noti.safeBannerText
&& obj.icon === noti.icon
&& obj.onClickCallback === noti.onClickCallback
&& obj.eNotificationType === noti.notificationType
&& obj.deleteable === noti.deletable
&& obj.withBanner === noti.withBanner
);
logger.info('[isNotificationExists] For the current notification: ', JSON.stringify(notification), ' isExists: ', isExists);

From MDN
The find() method returns the value of the first element in the array
that satisfies the provided testing function. Otherwise undefined is
returned.
The includes() method determines whether an array includes a certain
element, returning true or false as appropriate. It uses the
sameValueZero algorithm to determine whether the given element is
found.
To check if an item is duplicated in array
function hasDuplicates(array) {
return (new Set(array)).size !== array.length;
}

find() is not a suitable data structure here as it's return type is the element found not the flag(that whether it was found of not). You can use underscorejs _.contains functions for this.
http://underscorejs.org/#contains

Related

Filter an array of objects using the filter method with multiple tests

I'd like to filter an array of objects based on multiple tests. For this example, I want to filter an array of objects if the values for the keys aren't null, and that one value for one key is less than 90. I'm currently doing this with a for loop like so:
let filtered = []
for (let i = 0; i < articles.length; i++) {
if (articles[i].title !== null && articles[i].title.length <= 90 &&
articles[i].date !== null &&
articles[i].image !== null &&
articles[i].description !== null &&
articles[i].link !== null) {
filtered.push(articles[i])
}
}
But it's pretty clumpy and I know the filter method can achieve something similar. But I'm unsure if it can check multiple keys and their values with the same test whilst checking if a specific value passes an independent test too.
Try:
articles.filter(article =>
Object.values(article).every(x => (x !== null))
&& article.title.length <= 90
)
Let's break this down:
articles.filter(article => ...)
.filter is a function property of type that accepts a callback argument, which it calls for each item. Essentially, we're passing it a function - not executing it right away, which it can call at its leisure. It's sort of like:
let a = alert;
We're not calling the alert function, we're just saving it to a variable. In the case of .filter, we're using it as a pseudo-variable - an argument. Internally, all .filter is doing is:
Array.prototype.filter(callbackFunc) {
newArr = [];
for (i=0;i<this.length;i++){
if (callbackFunc(this[i]) === false){ // We're calling `callbackFunc` manually, for each item in the loop.
newArr.push(this[i]);
}
}
return newArr;
}
The next bit to explain is the actual callback function we're using. It's defined with ES6 arrow syntax, but it's the equivalent of:
articles.filter(function(article){
return Object.values(article).every(x => (x !== null))
&& article.title.length <= 90
})
The first line of the callback function, Object.values(article).every(x => (x !== null)), can be broken down to:
let values = Object.values(article); // Get the value of all of the keys in the object
function checkFunction(item){ // Define a helper function
return (x !== null); // Which returns if an item is _not_ null.
}
let result = values.every(checkFunction); // Run `checkFunction` on every item in the array (see below), and ensure it matches _all_ of them.
Finally, we just need to clarify what every does. It's another example of functional JS, where functions accept callback functions as parameters. The internal code looks like this:
Array.prototype.every(callbackFunc) {
for (i=0;i<this.length;i++){
if (callbackFunc(this[i]) === false){ // We're calling `callbackFunc` manually, for each item in the loop.
return false;
}
}
// In JS, returning a value automatically stops execution of the function.
// So this line of code is only reached if `return false` is never met.
return true;
}
And && article.title.length <= 90 should hopefully be self-explanatory: while .every returns a boolean value (true or false), a true will only be returned by the callback function to the filter if the second condition is also met, i.e if the every returns true and article.title.length <= 90
The filter method does exactly this: it takes a conditional (just like that in your if statement and adds it to the array if the condition is met. Your code almost matches the filter syntax exactly, actually:
let filtered = articles.filter(article =>
article.title !== null
article.title.length <= 90 &&
article.date !== null &&
article.image !== null &&
article.description !== null &&
article.link !== null);
yes filter can do this, it just takes a function and applies it to each item in the array
array.filter(x => x.title != null && ... etc)
the examples in this section is pretty much what you are doing https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter#Filtering_invalid_entries_from_JSON

How filter works in Mongoose?

I have seen a tutorial that execute filter in mongoose schema
I want to know how this really work.
const isConversationExist = user.conversations.filter(conversation => (
conversation.userOneId === request.payload.friendId || conversation.userTwoId === request.payload.friendId)
,).length > 0)
Just break it up into pieces starting with
user.conversations.filter(A)
The filter() method creates a new array with all elements that pass the test implemented by the provided function (A).
In the code, the provided function (A) is written using ES6 arrow notation
conversation => (
conversation.userOneId === request.payload.friendId || conversation.userTwoId === request.payload.friendId
)
which can be rewritten (but not always) as
function (conversation) {
return conversation.userOneId === request.payload.friendId || conversation.userTwoId === request.payload.friendId;
}
It basically says, for each element i.e. conversation, test if either of the conversation's user IDs is equal to request.payload.friendId.
If the test passes (as in function returns true), then the element will be added to the new array; otherwise, the element will be ignored.
user.conversations.filter(A).length
This would be the size of the new array with all conversations that pass the test.
user.conversations.filter(A).length > 0
This would be a boolean whether the new array has conversations that pass the test or not i.e. isConversationExist.

Detect if object is either an Array or typed array

I need to determine whether a given object is either an Array, or typed array such as Float32Array.
Currently I'm checking whether the .length property is defined, but this isn't always indicative of an array. Similar issues arise with existence checking of .forEach() or other methods.
Several instanceof checks would suffice, as done here - but I'm wondering if there is a simple built-in feature, e.g., a generic Array.isArray() function that does what I need.
function isArrayOrTypedArray(x) {
return Boolean(x && (typeof x === 'object') && (Array.isArray(x) || (ArrayBuffer.isView(x) && !(x instanceof DataView)));
}
Unfortunately, I don't believe there is.
You can do the instanceof checks you mentioned, or you could check the result of Object.prototype.toString.call(variable) to see if it's one of the predefind strings ("[object Array]", "[object Uint8Array]", etc.). (Edit: Ah, I see by following the link in your question that that's also demonstrated by that code.)
While I think, as T.J. Crowder already said, there's no built-in function, you should be able to combine Array.isArray and ArrayBuffer.isView to get the functionality you want:
function isArrayOrTypedArray(x) {
return Array.isArray(x) || (ArrayBuffer.isView(x) &&
Object.prototype.toString.call(x) !== "[object DataView]");
}
Array.isArray(x) returns true if x is an array. ArrayBuffer.isView(x)returns true if x is a typed array or DataView, so we just need to ignore the case where x is a DataView to get the function we want.
Demonstration:
function isArrayOrTypedArray(x) {
return Array.isArray(x) || (ArrayBuffer.isView(x) && Object.prototype.toString.call(x) !== "[object DataView]");
}
console.log(isArrayOrTypedArray()); // false
console.log(isArrayOrTypedArray({})); // false
console.log(isArrayOrTypedArray(null)); // false
console.log(isArrayOrTypedArray(undefined)); // false
console.log(isArrayOrTypedArray(new ArrayBuffer(10))); // false
console.log(isArrayOrTypedArray([])); // true
console.log(isArrayOrTypedArray([1,2,3,4,5])); // true
console.log(isArrayOrTypedArray(new Uint8Array())); // true
console.log(isArrayOrTypedArray(new Float32Array())); // true
console.log(isArrayOrTypedArray(new Int8Array(10).subarray(0, 3))); // true
var buffer = new ArrayBuffer(2);
var dv = new DataView(buffer);
console.log(isArrayOrTypedArray(dv)); // false
You could do something like this:
function isArray(array) {
if((array.length || array.length === 0) && (array.constructor !== String)) {
return true;
}
return false;
}
Note that a String also has a length property and we need to exclude that, hence the constructor check.
To determine if x is an ArrayBuffer,
You can take advantage of the fact that new DataView(x) throws "TypeError: First argument to DataView constructor must be an ArrayBuffer" if x isn't an ArrayBuffer.
In other words, simply do:
function isArrayBuffer(x) {
try {
new DataView(x);
return true;
}
catch (TypeError) {
return false;
}
}
And to test if a thing is a TypedArray,
I believe ArrayBuffer.isView does the job.
You can use obj.constructor.name as a way of getting the object's name rather than an instanceof matching ladder.
What all these arrays have in common is they have Array in their classname and a array.length property which is a number.
function isArray(x) {
if (typeof x.length === 'number'
&& x.constructor.name.includes('Array')) {
return true;
}
return false;
}
This works in later versions of Javascript/Node anyway.
You could use name.includes if your JS version supports it.
Array.constructor.name is Array for [] and others like Uint8Array for typed arrays.
In other versions of JavaScript you may need obj.prototype.constructor.name.

Javascript function return value undefined

I am using a function to go through an array of objects and filter out the one that matches certain criteria.
var thread = underscore.find(threads, function (th) {
function result(threadArray){
threadArray.forEach(function(threads){
if (threads.owner.id === ownerId && threads.name.toLowerCase() == threadName) {
console.log(threads)
return threads
}
});
};
console.log(result(th));
return th.owner.id === ownerId && th.name.toLowerCase() === threadName;
});
'th' is the array of objects. Stepping through it, I can see that the array of objects is being iterated over using the forEach function, and that my if logic is successfully filtering out just one object because I can see it in my console "console.log(threads)", but when I try to console the return value of the function by invoking it, "console.log(result(th))", it comes back as undefined, and I can't figure out why.

Checking if an object is array-like

Is there a way to check if an object is "array-like", like for these types of objects:
Arrays (duh)
Typed Arrays (Uint8Array, etc), these will return false when Array.isArray is used
arguments object
NodeLists*
There are a few other ones that I can't think of off-hand
I suppose you could check for the presence of a .length property, but non-array-like objects can contain the .length property. I guess the thing these all share in common is the array accessor.
As best I've found in my research on this topic, you have only a couple choices:
You can look only at the .length property and accept any object that seems to have an appropriate .length property that isn't any other things you know you should eliminate (like a function).
You can check for specific array-like objects (HTMLCollection, nodeList) and bias in favor of them.
Here are two options for the first method - one that doesn't accept a zero length and one that does (these incorporate suggestions by gilly3 and things we see in jQuery's similar function):
// see if it looks and smells like an iterable object, but don't accept length === 0
function isArrayLike(item) {
return (
Array.isArray(item) ||
(!!item &&
typeof item === "object" &&
item.hasOwnProperty("length") &&
typeof item.length === "number" &&
item.length > 0 &&
(item.length - 1) in item
)
);
}
This, of course, reports false for items with .length === 0, If you want to allow .length === 0, then the logic can be made to include that case too.
// see if it looks and smells like an iterable object, and do accept length === 0
function isArrayLike(item) {
return (
Array.isArray(item) ||
(!!item &&
typeof item === "object" &&
typeof (item.length) === "number" &&
(item.length === 0 ||
(item.length > 0 &&
(item.length - 1) in item)
)
)
);
}
Some test cases: http://jsfiddle.net/jfriend00/3brjc/
2) After checking to see that it's not an actual array, you can code to check for specific kinds of array-like objects (e.g. nodeList, HTMLCollection).
For example, here's a method I use when I want to make sure I include nodeList and HTMLCollection array-like objects:
// assumes Array.isArray or a polyfill is available
function canAccessAsArray(item) {
if (Array.isArray(item)) {
return true;
}
// modern browser such as IE9 / firefox / chrome etc.
var result = Object.prototype.toString.call(item);
if (result === "[object HTMLCollection]" || result === "[object NodeList]") {
return true;
}
//ie 6/7/8
if (typeof item !== "object" || !item.hasOwnProperty("length") || item.length < 0) {
return false;
}
// a false positive on an empty pseudo-array is OK because there won't be anything
// to iterate so we allow anything with .length === 0 to pass the test
if (item.length === 0) {
return true;
} else if (item[0] && item[0].nodeType) {
return true;
}
return false;
}
You can check if the object is iterable :
function isIterable(o){
return (o!=null && typeof(o[Symbol.iterator])==='function');
}
Careful though, returns true for strings. If that's a problem, exclude them :
function isIterable(o){
return (o!=null && typeof(o[Symbol.iterator])==='function' && typeof(o)!=='string');
}
Then either access the elements using the iterator or if you want to use the regular array[0] way, just add a check for length property.
Complete isArrayLike function :
function isArrayLike(a){
return (
a!=null &&
typeof(a[Symbol.iterator])==='function' &&
typeof(a.length)==='number' &&
typeof(a)!=='string'
);
}
In JavaScript an array-like object is an object containing a length property typically of type number with a non-negative whole value no greater than 2^53-1. Array-like objects with lengths greater than zero also typically contain additional properties beginning at [0] and going up to [n-1] where n = the value of length.
From MDN:
The term array-like object refers to any object that doesn't throw during the length conversion process described [below]. In practice, such object is expected to actually have a length property and to have indexed elements in the range 0 to length - 1. (If it doesn't have all indices, it will be functionally equivalent to a sparse array.)
Normalization of the length property
The length property is converted to an integer and then clamped to the range between 0 and 2^53 - 1.
Array-like objects also differ from other objects in that Array methods such as Array.prototype.push can be called indirectly on them using Function.prototype.call().
Also from MDN:
Array methods cannot be called directly on array-like objects. But you
can call them indirectly using Function.prototype.call().
Calling Array functions on an object without a normalized length property changes the value of length. Therefore I thought it best that my function return false if the length property is not already an integer between 0 and 2^53 - 1. The length property cannot be a BigInt as that would throw a TypeError.
This function also checks for existence of the last element to help rule out objects that have a length property but aren't intended to be array-like.
Tested and works in old browsers (ES3 and up).
function isArrayLike(obj) {
var i;
return /( Arguments|NodeList|Collection|Array)\b/.test(({}).toString.call(obj))// Always array-like
// Confirm obj is an object but not a function or null
|| !!obj && typeof obj=="object"
// Confirm that Array methods can be called indirectly on obj
// without throwing or changing the value of obj.length
// by confirming obj.length (i) is a normalized integer
// Usually this means i is in the range 0 - 9007199254740991
&& [].push.call({length:Number(i=obj.length)})===i
// And when non-zero confirm i>0 and i-1 is a property in obj
&& (!i || i>0 && i-1 in obj);
}
Well, it depends what you mean by array-like. Probably something you could iterate with a for loop like this:
for (var i=0, l=obj.length; i<l; ++i) {
var item = obj[i];
}
So then the test is simple:
function isArrayLike(obj) {
if (!obj) return false;
var l = obj.length;
if (typeof l != 'number' || l < 0) return false;
if (Math.floor(l) != l) return false;
// fast check
if (l>0 && !((l-1) in obj)) return false;
// more complete check (optional)
for (var i=0; i<l; ++i) {
if (!(i in obj)) return false;
}
return true;
}
Of course, this won't catch arrays which are sparsely populated, but then again, are they really being used as arrays? NodeLists and the like won't be sparsely populated.
Enjoy!
There is a way to check if an object is array-like or not, even if there are no elements in it, using this function here:
isArrayLike = function (_) {
_[0] = 0; return [].slice.call(_).length >= Object.values(_).length;
}
This uses a little hack I accidentally discovered that allows you to determine if an object is (1) an array, (2) array-like, or (3) object / object-like.
The only downside is that it does not work correctly for array-likes that have object-like properties added, such as arguments
Technically, (pretty much) every object is "array-like" (because of type coercion of undefined) according to the standard (ECMAScript 2015 Language Specification §7.3.17, CreateListFromArrayLike (obj [, elementTypes] )):
7.3.17 CreateListFromArrayLike (obj [, elementTypes] )
The abstract operation CreateListFromArrayLike is used to create a List value whose elements are provided by the indexed properties of an array-like object, obj. The optional argument elementTypes is a List containing the names of ECMAScript Language Types that are allowed for element values of the List that is created. This abstract operation performs the following steps:
ReturnIfAbrupt(obj).
If elementTypes was not passed, let elementTypes be (Undefined, Null, Boolean, String, Symbol, Number, Object).
If Type(obj)
is not Object, throw a TypeError exception.
Let len be ToLength(Get(obj, "length")).
ReturnIfAbrupt(len).
Let list be an empty List.
Let index be 0.
Repeat while index < len
Let indexName be ToString(index).
Let next be Get(obj, indexName).
ReturnIfAbrupt(next).
If Type(next)
is not an element of elementTypes, throw a TypeError exception.
Append next as the last element of list.
Set index to index + 1.
Return list.
Generated via https://www.browserling.com/tools/html-to-markdown
An Array is a value that has following properties:
Its of type object
It has length property which is equal or greater than 0.
All the value based keys are numeric and is greater than or equal to 0.
Exceptions:
length
Value is a function.
function isArrayLike(value) {
if (typeof value === "object" && !!value) {
const isArray = Array.isArray(value);
const allNumericKeys = Object.keys(value).every((k) =>
(!isNaN(k) && +k >= 0) ||
k === "length" ||
typeof value[k] === "function"
)
const hasLength = value.length > 0
return isArray || (allNumericKeys && hasLength)
}
return false
}
console.log('Array: ', isArrayLike([]))
console.log('Array Like: ', isArrayLike({1: 'a', 2: 'b', length: 3}))
console.log('Array Like with function: ', isArrayLike({1: 'a', 2: 'b', length: 3, foo: () => {} }))
console.log('Array Like with negative keys: ', isArrayLike({ "-1": 'a', "-2": 'b', length: 1}))
console.log('Array Like without length:', isArrayLike({1: 'a', 2: 'b' }))
console.log('Node List: ', isArrayLike(document.querySelectorAll('p')))
console.log('null: ', isArrayLike(null))
console.log('String: ', isArrayLike('Test'))
console.log('Number: ', isArrayLike(123))
<div>
<p></p>
<p></p>
<p></p>
<p></p>
</div>
I say nothing beats the simplicity and expressiveness of extending native objects:
Object.prototype.isArrayLike = function(){ return false; };
Array.prototype.isArrayLike = function(){ return true; };
NodeList.prototype.isArrayLike = function(){ return true; };
HTMLCollection.prototype.isArrayLike = function(){ return true; };
This approach could cause conflicts between frameworks, however, I recommend keeping your distance from a framework whose isArrayLike function does not what the name suggests.

Categories

Resources