简体   繁体   中英

How to create a Javascript (es5) constructor function containing an asynchronous call

I'm trying to create a constructor function that includes a call to indexedDB or localStorage to retrieve some data if it exists and then populate it's values, either from the cache or the given (default) data.

I had it working, but somehow broke my code. Given that my method of programming is "hit it with a stick until it works", the code is an absolute mess. So I have simplified the problem down to a contrived example, which fails in the same way.

Removing the async function (setTimeout) fixes the problem, but then my code won't work.

var person = (function(){
    function privateFn (value){
        return value;
    }
    function private_asyncFn(value, callback){
        setTimeout(function () {
            return callback(value);
        }, 10);
    }
    var DATA, NUM;
    var personConstructor = function(name, age, gender, callback){
        this.name = privateFn(name);
        this.gender = privateFn(gender);
        DATA = DATA || [];
        NUM = NUM || 0;
        this.num = NUM;
        private_asyncFn(age, function(ret){
            DATA[NUM++] = { name: this.name, age: ret };
            if (callback instanceof Function) return callback(this.name);
        }.bind(this));
    };
    personConstructor.prototype.getAge = function () {
        if (this.gender === "male") return DATA[this.num].age;
        else return DATA[this.num].age < 20 ? 18 : (Math.floor(DATA[this.num].age / 10) - 1) + "9 and holding";
    };
    return personConstructor;
})();

var name1;
var person1 = new person("jill", 32, "female", function(name){
    name1 = name;
});
var age1 = person1.getAge(); //Uncaught TypeError: Cannot read property 'age' of undefined????

I see other code on SO using classes and promises and async/await, but I don't understand how that all works, so this code is a bit "old school", sorry about that.

In this example, can you retrieve the age of the person, immediately following the initialization of a new person?

There is no way to make an asynchronous constructor. What you want to do instead is probably create an asynchronous function that will yield your constructed object after the asynchronous operation is completed. I'd give you a more concrete example, but frankly, I don't understand what the async part of your code is attempting to do. Here's an example of how you might accomplish this, though:

function Person(name, age, gender) {
  this.name = name
  this.gender = gender
  this.age = age
}

Person.prototype.getAge = function() {
  return this.age
}

Person.create = function create(name, age, gender, done) {
  var person = new Person(name, age, gender)
  setTimeout(function () {
    done(null, person)
  }, 1000)
}

Person.create("jill", 32, "female", function (person) {
  person.getAge()
})

I would recommend using Promises and ES6 classes if you don't need to support very old browsers.

class Person {
  static create(name, age, gender) {
    return new Promise(resolve => {
      const person = new Person(name, age, gender)
      resolve(person)
    })
  }

  constructor(name, age, gender) {
    this.name = name
    this.age = age
    this.gender = gender
  }

  getAge() {
    return this.age
  }
}

Person.create("jill", 32, "female")
  .then((person) => {
    person.getAge()
  })

It would be helpful when you post your next SO question to give a real world example of what your code is trying to achieve - getAge returning different results depending on gender doesn't make much sense:)

By moving all of the async logic into an init function that is called after creating a new person keeps the constructor synchronous. Then getAge can be called from inside the init callback function using either this.getAge() or person1.getAge() (because person1 exists now)

var person = (function(){

    function privateFn (value){
        return value;
    }

    function private_asyncFn(value, callback){
        setTimeout(function () {
            return callback(value);
        }, 10);
    }

    var DATA, NUM;

    //removed all data inputs and callback from constructor function
    var personConstructor = function(){

        //initialise publically accessible data    
        this.name = null;
        this.gender = null;

        //initialise blank data store
        DATA = DATA || [];
        NUM = NUM || 0;
        this.num = NUM;
        DATA[NUM++] = { 
            age: null
        };
    };

    //created new init function
    personConstructor.prototype.init = function (name, age, gender, callback){

        this.name = privateFn(name);
        this.gender = privateFn(gender);

        private_asyncFn(age, function(ret){

            DATA[this.num].age = ret;

            //note: .call(this,...) on the callback to use "this" in the callback
            if (callback instanceof Function) return callback.call(this, this.name);

        }.bind(this));
    };

    personConstructor.prototype.getAge = function () {
        if (this.gender === "male") return DATA[this.num].age;
        else return DATA[this.num].age < 20 ? 18 : (Math.floor(DATA[this.num].age / 10) - 1) + "9 and holding";
    };

    return personConstructor;
})();

var name1, age1;
var person1 = new person();
person1.init("jill", 32, "female", function(name){
    name1 = name;
    age1 = this.getAge();
    console.log(age1);//29 and holding, YES!
});

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