0

javascript Map() has thrown me off. My question is about javascript Map() - set, get and has(). I thought I was an expert on it, but apparently not...

Situation: I have a 'global' map - a map saved on a different .js file that gets 'required'. This map's keys are the _id of MongoDB documents. The values of this map are class objects of node-schedule - an npm package. The value part can be ignored since my problem is with .get(), .has() and .set()

Here is my method that (fails to) cancel jobs:

    cancelJob: async function (id) {
        console.log('---- inside cancelJob ---');
        console.log('id = ', id);
        console.log('map = ', config.map);
        console.log('map size = ', config.map.size);
        if (config.map.has(id)) {
            console.log('---- map has id ---');
            let job = config.map.get(id);
            console.log('job = ', job);
            await job.cancel();
            config.map.delete(id);
            console.log('---- deleted 1 from map ---');
        }
    },

The if statement always returns false. I previously tried the .get() and checked if the returned value is not undefined, but it was undefined. let me share the console log of my server - resulting from the above code, after I do .set() and (failed to do) .delete() a couple of times:

id =  5f9c3b171a5d1b26089d60fe
map =  Map {
  5f9c3b171a5d1b26089d60fe => Job {
    job: [Function],
    callback: false,
    name: '<Anonymous Job 1>',
    trackInvocation: [Function],
    stopTrackingInvocation: [Function],
    triggeredJobs: [Function],
    setTriggeredJobs: [Function],
    cancel: [Function],
    cancelNext: [Function],
    reschedule: [Function],
    nextInvocation: [Function],
    pendingInvocations: [Function]
  },
  5f9c3b171a5d1b26089d60fe => Job {
    job: [Function],
    callback: false,
    name: '<Anonymous Job 2>',
    trackInvocation: [Function],
    stopTrackingInvocation: [Function],
    triggeredJobs: [Function],
    setTriggeredJobs: [Function],
    cancel: [Function],
    cancelNext: [Function],
    reschedule: [Function],
    nextInvocation: [Function],
    pendingInvocations: [Function]
  }
}
map size =  2

As you can see, map doesn't store (!?) the values to the same key slot? I am confused. I thought I learned that keys are unique, and that it should find the key... Please help me figure this out if you can :)

PS: I did ctrl + F on the ID value, and it turns out the ID string is the same for all the keys and the ID parameter.


Edit 1: Here is the method that sets the map:

convertDocumentToScheduledJob: function (dbJob) {
 // prepare date and rule here...
 let j = schedule.scheduleJob(dbJob.recurrence == 'no-recurrence' ? date : rule, function () {
            // does stuff
        })
        config.map.set(dbJob._id, j);
}

I am using this convertDocumentToScheduledJob method to set/insert map key-value pairs. This whole thing is part of a full-stack web app, so there are multiple controllers that call these 2 methods, but I'm pretty sure those don't directly affect anything here. They just invoke these 2 methods.

Again, as seen in my PS message above, when I console log the config.map, it shows 2 map entries with the same keys. ~~Isn't that worrying?~~


Edit 2: @FelixKling solved this problem. The key was of data type, Object. I had to convert that to String to resolve this. Thank you.

    console.log('Type of job_id = ', typeof dbJob._id);
    let strId = JSON.stringify(dbJob._id);
    console.log('Type of strID = ', typeof strId);
    config.map.set(strId, j);
Tenzin Thabkhae
  • 75
  • 1
  • 10
  • Key's are unique in a map, there is something missing here, can you show the code were your filling the map. – Keith Nov 02 '20 at 15:12
  • Sure. Here is the code that sets the map: ```javascript convertDocumentToScheduledJob: function (dbJob) { let j = schedule.scheduleJob(dbJob.recurrence == 'no-recurrence' ? date : rule, function () { // does stuff }) config.map.set(dbJob._id, j); ``` – Tenzin Thabkhae Nov 02 '20 at 15:16
  • What is `typeof dbJob._id` ? – Felix Kling Nov 02 '20 at 15:31
  • @FelixKling nice catch. Turns out the typeof dbJob._id is `object`. Although it should have been a string. This is a major breakthrough. Thanks. Edit: dbJob is a MongoDB document. – Tenzin Thabkhae Nov 02 '20 at 15:35
  • *"... it shows 2 map entries with the same keys. Isn't that worrying?"* Since it's not possible that a map has two entries with the same keys, the only conclusion can be that the keys must be different, which would be the case with a string object for example. But `dbJob._id` might be any object that possibly implements `toString`. – Felix Kling Nov 02 '20 at 15:37
  • Thank you. I updated my code to resolve this problem. It's very reasonable to check the data type of the map key and not to take a MongoDB string value to have the string datatype as granted. – Tenzin Thabkhae Nov 02 '20 at 15:44

1 Answers1

0

@FelixKling solved this question in the comments.

The key was of data type Object. I had to convert that to String to resolve this. Thank you.

Solution 1 (incomplete):

console.log('Type of job_id = ', typeof dbJob._id);
let strId = JSON.stringify(dbJob._id);
console.log('Type of strID = ', typeof strId);
config.map.set(strId, j);
  • This eliminates the duplicated key bug. New map entries now properly replace the old map entry with the same key.
  • The problem still persists when I invoke cancelJob(id) where the id is a basic string value. JSON.stringified string is different from regular string. (it has an extra double quote)
  • MongoDB documentation shows a way to convert ._id to regular string via the .toString() method. [Sources: StackOverFlow source; MongoDB Documentation]

Solution 2:

    convertDocumentToScheduledJob: function (dbJob) {
        // ...
        let strId = dbJob._id.toString();
    },
    cancelJob: async function (raw_id) {
        // ...
        let id = typeof raw_id == 'object' ? raw_id.toString() : raw_id;
    },

Tenzin Thabkhae
  • 75
  • 1
  • 10