web-dev-qa-db-fra.com

Est-ce une mauvaise pratique de renvoyer une promesse par une fonction constructeur?

J'essaie de créer un constructeur pour une plate-forme de blogging qui contient de nombreuses opérations asynchrones. Celles-ci vont de récupérer les articles des répertoires, de les analyser, de les envoyer par le biais de moteurs de gabarits, etc.

Ma question est donc la suivante: ne serait-il pas sage que ma fonction constructeur retourne une promesse au lieu d’un objet de la fonction qu’ils ont appelée new contre.

Par exemple:

var engine = new Engine({path: '/path/to/posts'}).then(function (eng) {
   // allow user to interact with the newly created engine object inside 'then'
   engine.showPostsOnOnePage();
});

Maintenant, l’utilisateur peut aussi pas fournir un supplément. Maillon de chaîne Promise:

var engine = new Engine({path: '/path/to/posts'});

// ERROR
// engine will not be available as an Engine object here

Cela peut poser un problème, car l'utilisateur peut ne pas comprendre pourquoienginen'est pas disponible après la construction.

La raison d'utiliser une promesse dans le constructeur est logique. Je veux que tout le blog fonctionne après la phase de construction. Cependant, cela ressemble à une odeur de ne pas avoir accès à l'objet immédiatement après avoir appelé new.

J'ai discuté d'utiliser quelque chose du type engine.start().then() ou engine.init() qui renverrait la promesse à la place. Mais ceux-ci semblent aussi malodorants.

Edit: C'est dans un projet Node.js.

129
adam-beck

J'ai rencontré le même problème et suis venu avec cette solution simple.

Au lieu de renvoyer une promesse du constructeur, placez-la dans la propriété this.initialization, comme ceci:

function Engine(path) {
  var engine = this
  engine.initialization = Promise.resolve()
    .then(function () {
      return doSomethingAsync(path)
    })
    .then(function (result) {
      engine.resultOfAsyncOp = result
    })
}

Ensuite, placez chaque méthode dans un callback exécuté après l’initialisation, comme ceci:

Engine.prototype.showPostsOnPage = function () {
  return this.initialization.then(function () {
    // actual body of the method
  })
}

A quoi ça ressemble du point de vue du consommateur API:

engine = new Engine({path: '/path/to/posts'})
engine.showPostsOnPage()

Cela fonctionne parce que vous pouvez enregistrer plusieurs rappels dans une promesse et qu'ils s'exécutent soit après la résolution de celle-ci, soit, si elle est déjà résolue, au moment de l'attachement du rappel.

Voici comment mongoskin fonctionne, sauf qu’il n’utilise pas les promesses.


Edit: Depuis que j'ai écrit cette réponse, je suis tombée amoureuse de la syntaxe ES6/7, ce qui en fait un autre exemple. Vous pouvez l'utiliser aujourd'hui avec Babel.

class Engine {

  constructor(path) {
    this._initialized = this._initialize()
  }

  async _initialize() {
    // actual async constructor logic
  }

  async showPostsOnPage() {
    await this._initialized
    // actual body of the method
  }

}

Edit: vous pouvez utiliser ce modèle de manière native avec les nœuds 7 et --harmony flag!

10
phaux

Pour éviter la séparation des problèmes, utilisez une fabrique pour créer l'objet. 

class Engine {
    constructor(data) {
        this.data = data;
    }

    static makeEngine(pathToData) {
        return new Promise((resolve, reject) => {
            getData(pathToData).then(data => {
              resolve(new Engine(data))
            }).catch(reject);
        });
    }
}
3
The Farmer

La valeur renvoyée par le constructeur remplace l'objet que le nouvel opérateur vient de générer. Il n'est donc pas judicieux de renvoyer une promesse. Auparavant, une valeur de retour explicite du constructeur était utilisée pour le modèle singleton.

La meilleure façon dans ECMAScript 2017 est d'utiliser une méthode statique: vous avez un processus, qui est la numéralité de la statique.

Quelle méthode à exécuter sur le nouvel objet après le constructeur peut être connue uniquement par la classe elle-même. Pour encapsuler cela dans la classe, vous pouvez utiliser process.nextTick ou Promise.resolve, en retardant l'exécution ultérieure pour permettre l'ajout d'écouteurs et d'autres éléments dans Process.launch, l'invocateur du constructeur.

Étant donné que presque tout le code est exécuté dans une promesse, les erreurs se retrouveront dans Process.fatal.

Cette idée de base peut être modifiée pour répondre à des besoins d’encapsulation spécifiques.

class MyClass {
  constructor(o) {
    if (o == null) o = false
    if (o.run) Promise.resolve()
      .then(() => this.method())
      .then(o.exit).catch(o.reject)
  }

  async method() {}
}

class Process {
  static launch(construct) {
    return new Promise(r => r(
      new construct({run: true, exit: Process.exit, reject: Process.fatal})
    )).catch(Process.fatal)
  }

  static exit() {
    process.exit()
  }

  static fatal(e) {
    console.error(e.message)
    process.exit(1)
  }
}

Process.launch(MyClass)
0
Harald Rudell