0

In router/user.js for routing:

router.post('/register', auth.optional, (req, res, next) => {
  const {
    body: { user }
  } = req

  // validation code skipped for brevity

  const finalUser = new User(user)
  finalUser.setPassword(user.password)

  // to save the user in Mongo DB
  return finalUser.save().then(() => res.json({ user: finalUser.toAuthJSON() }))
})

Post request sent with a body of:

{
    "user":{
        "email":"leon@idiot.com",
        "password": "123abc"
    }
}

In model/User.js for database schema:

const UserSchema = new Schema({
  email: String,
  hash: String,
  salt: String
})


// Please note: this is a regular/normal function definition
UserSchema.methods.setPassword = function (password) {
  // this references the UserSchema created
  this.salt = crypto.randomBytes(16).toString('hex')
  console.log(`salt: ${this.salt}`)
  this.hash = crypto
    .pbkdf2Sync(password, this.salt, 10000, 512, 'sha512')
    .toString('hex')
  console.log(`hash: ${this.hash}`)
}

Everything works fine now. The log output as:

salt: e7e3151de63fc8a90e3621de4db0f72e
hash: 19dd9fdbc78d0baf20513b3086976208ab0f9eee6d68f3c71c72cd123a06459653c24c11148db03772606c40ba4846e2f9c6d4f1014d329f01d22805fc988f6164fc13d4157394b118d921b9cbd742ab510e4d2fd4ed214a0d523262ae2b2f80f6344fbd948e8c858f95ed9706952db90d415312156a994c65c42921afc8c3e5b1b24a923219445eec8ed62de313ab3d78dc93b715689a552b6449870c5bfcc3bec80c4438b1895cab41f92ef681344ac8578de476a82aa798730cf3a6ef86973a4364a8712c6b3d53ce67ffffd7569b9ade5db09ad95490354c6f7194fdd9d8f8a1cb7ccddf59e701198a1beee59a2dd6afb90ae50e26ea480e9a6d607e4b37857a02016ee4d692d468dd9a67499547eb03fc6cfa676686f7990c2251c9516459288c55584138aed56a5df6c4692f7ef6925e8f3d6f6a0c780c4d80580447f2b1258bea799a8c7eb9da878ab70a94c4227ec03d18d56b2722c315d0e2b2681d81d78d4213288f7305cbbfa377c3b2eb75e0f0b093e6067b14adce4a01f0a7bde8515350a1c987739c12574ec4c49008510e2e7e5534f9b76d15b1af68e43ef54e6b8a1bea859aafd23d6b6bc61d5b1965004cd6dd933545cf755f3e6dfc8f230f37a79a8bc006b9b14465b1b08d60cb45ef3b6a1b73f5afac90bdc58d5ec15c7596dc7e8d503f8dfbd6a3289cf997da2031389c7f3d165e34b29178f3daf76d
3

But it does not work with such a definition:

UserSchema.methods.setPassword = password => {
  // what this reference is undefined, so are ones below
  this.salt = crypto.randomBytes(16).toString('hex')
  console.log(`salt: ${this.salt}`)
  this.hash = crypto
    .pbkdf2Sync(password, this.salt, 10000, 512, 'sha512')
    .toString('hex')
  console.log(`hash: ${this.hash}`)
}

The error is:

{
    "errors": {
        "message": "Cannot set property 'salt' of undefined",
        "error": {}
    }
}

which means what this referenced is undefined.

What I find online is that fat arrow functions explicitly prevent binding of this, and it's a problem of scopes, that this in fat arrow functions has a scope of its immediate object. But I cannot say that I understand it very well.
1. In this case, what is the scope of this in fat arrow functions?
2. What is the scope, of this, in normal function definitions?
3. How to access the object, in this case: UserSchema, properties (forgive me for less proper words) in fat arrow functions as one does in normal function definitions?

These posts are quite helpful:
Are 'Arrow Functions' and 'Functions' equivalent / exchangeable?
How does the “this” keyword work?

But I am still expecting answers to my specific questions in particular cases, before figuring them out.

Leon
  • 365
  • 5
  • 15
  • Possible duplicate of [Are 'Arrow Functions' and 'Functions' equivalent / exchangeable?](https://stackoverflow.com/questions/34361379/are-arrow-functions-and-functions-equivalent-exchangeable) – CertainPerformance Jun 17 '19 at 02:54
  • Inside an arrow function, `this` is inherited from the outer scope, also see https://stackoverflow.com/questions/3127429/how-does-the-this-keyword-work – CertainPerformance Jun 17 '19 at 02:55

1 Answers1

2

The core of your misunderstanding is this:

that this in fat arrow functions has a scope of immediate object

Wrong. It's context is resolved in the scope of the currently executing function/environment.

Examples:

// global scope outside of any function:

let foo = {};

// Define a method in global scope (outside of any function)
foo.a = () => {
    console.log(this); // undefined
}

// Return a function from scope of a method:
foo.b = function () {
    // remember, "this" in here is "foo"

    return () => {
        console.log(this); // foo - because we are in scope foo.c()
    }
}

foo.a();   // undefined
foo.b()(); // foo

For arrow functions, it is not what object the function belongs to that matters but where it is defined. In the second example, the function can completely not belong to foo but will still print foo regardless:

bar = {};
bar.b = foo.b();

bar.b(); // will log "foo" instead of "bar"

This is the opposite of regular functions which depend on how you call them instead on where you define them:

// Defined in global scope:
function c () {
    console.log(this);
}

bar.c = c;
bar.c(); // will log "bar" instead of undefined because of how you call it

Note

Note that there are two very different concepts here that are intermingled - context (what value "this" has) and scope (what variables are visible inside a function). Arrow function uses scope to resolve context. Regular functions do not use scope but instead depend on how you call them.

Questions

Now to answer some of your questions:

  1. In this case, what is the scope of this in fat arrow functions?

As I said. Scope and this are two unrelated concepts. The concept behind this is object/instance context - that is, when a method is called what object is the method acting on. The concept of scope is as simple as what are global variables and what variables exist only inside a specific function and it can evolve to more complicated concepts like closures.

So, since scope is always the same, the only difference is in arrow functions, its context (its this) is defined by the scope. That is, when the function is being declared, where is it declared? At the root of the file? Then it has global scope and this equals "undefined". Inside another function? Then it depends on how that function is called. If it was called as a method of an object such as UserSchema.methods for example if UserSchema.methods.generatePasswordSetter() returns an arrow function, then that function (let's call it setPassword()) will have it's this point to the correct object.

  1. What is the scope, of this, in normal function definitions?

Based on my explanation above I can only sat that scope is not involved with the value of this in normal functions. For a more detailed explanation of how this works see my answer to this other question: How does the "this" keyword in Javascript act within an object literal?

  1. How to access the object, in this case: UserSchema, properties (forgive me for less proper words) in fat arrow functions as one does in normal function definitions?

The way it's defined it is not possible. You need to define it from a regular function that has it's this pointing to UserSchema:

UserSchema.methods.generatePasswordSetter = function () {
    return (password) => { /* implementation... */}
}

But this is probably not what you want. To do what you want you only need to stop using arrow functions in this case. Regular functions still exist for use-cases such as this.

slebetman
  • 93,070
  • 18
  • 116
  • 145