首页 文章

让构造函数返回Promise是不好的做法吗?

提问于
浏览
124

我正在尝试为博客平台创建一个构造函数,它内部有许多异步操作 . 这些包括从目录中获取帖子,解析它们,通过模板引擎发送它们等等 .

所以我的问题是,让我的构造函数返回一个promise而不是一个名为 new 的函数的对象是不明智的 .

例如:

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();
});

现在,用户还可以 not 提供补充Promise链接:

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

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

这可能会造成问题,因为用户可能会混淆为什么 engine 在施工后不可用 .

在构造函数中使用Promise的原因是有道理的 . 我希望整个博客在构建阶段后正常运行 . 然而,在调用 new 之后,似乎几乎无法访问该对象的气味 .

我已经使用了 engine.start().then()engine.init() 这样的东西进行了辩论,而这些东西将会返回Promise . 但那些看起来也很臭 .

编辑:这是在Node.js项目中 .

4 回答

  • 0

    是的,这是一个不好的做法 . 构造函数应该返回其类的实例,没有别的 . 否则会破坏new operator和继承 .

    而且,构造函数应该只创建和初始化一个新实例 . 它应该设置数据结构和所有特定于实例的属性,但是 not execute 任何任务 . 如果可能的话,它应该是一个没有副作用的pure function,具有所有的好处 .

    如果我想从构造函数中执行某些操作,该怎么办?

    这应该是你 class 的方法 . 你想改变全球状态?然后显式调用该过程,而不是生成对象的副作用 . 实例化后,此调用可以立即执行:

    var engine = new Engine()
    engine.displayPosts();
    

    如果该任务是异步的,您现在可以轻松地从该方法返回其结果的承诺,以便轻松等待它完成 .
    但是,当方法(异步)改变实例并且其他方法依赖于此时,我不建议使用此模式,因为这会导致需要等待(即使它们快速进行内部队列管理,也会变为异步) . 不要将实例编码为存在但实际上不可用 .

    如果我想异步将数据加载到我的实例中该怎么办?

    问问自己:你真的需要没有数据的实例吗?你能以某种方式使用它吗?

    如果答案是“否”,则在获得数据之前不应创建它 . 使数据ifself成为构造函数的参数,而不是告诉构造函数如何获取数据(或传递数据的承诺) .

    然后,使用静态方法加载数据,从中返回一个promise . 然后将一个包含数据的调用链接到新实例中:

    Engine.load({path: '/path/to/posts'}).then(function(posts) {
        new Engine(posts).displayPosts();
    });
    

    这使得获取数据的方式具有更大的灵活性,并且大大简化了构造函数 . 同样,您可以编写返回 Engine 实例的promise的静态工厂函数:

    Engine.fromPosts = function(options) {
        return ajax(options.path).then(Engine.parsePosts).then(function(posts) {
            return new Engine(posts, options);
        });
    };
    
    …
    
    Engine.fromPosts({path: '/path/to/posts'}).then(function(engine) {
        engine.registerWith(framework).then(function(framePage) {
            engine.showPostsOn(framePage);
        });
    });
    
  • 160

    构造函数的返回值替换new运算符刚刚生成的对象,因此返回promise不是一个好主意 . 以前,构造函数的显式返回值用于单例模式 .

    ECMAScript 2017中更好的方法是使用静态方法:你有一个进程,这是静态的数字 .

    在构造函数之后对新对象运行哪种方法只能为类本身所知 . 要将其封装在类中,您可以使用process.nextTick或Promise.resolve,推迟进一步执行,允许在Process.launch(构造函数的调用者)中添加侦听器和其他内容 .

    由于几乎所有代码都在Promise内部执行,因此错误将最终出现在Process.fatal中

    可以修改此基本思想以适应特定的封装需求 .

    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)
    
  • 9

    我遇到了同样的问题,想出了这个简单的解决方案 .

    而不是从构造函数返回Promise,将它放在 this.initialization 属性中,如下所示:

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

    然后,将每个方法包装在初始化后运行的回调中,如下所示:

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

    它从API消费者的角度看起来如何:

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

    这是有效的,因为您可以向承诺注册多个回调,它们在结算后运行,或者如果已经解决,则在附加回调时运行 .

    这是mongoskin的工作原理,除了它实际上并没有使用promises .


    Edit: 自从我写了回复之后,我又用了另一个例子 . 今天你可以和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 :您可以在节点7和 --harmony 标志中原生使用此模式!

  • 3

    为避免关注点分离,请使用工厂创建对象 .

    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);
            });
        }
    }
    

相关问题