Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/javascript/401.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Javascript 让构造函数返回一个承诺是一种不好的做法吗?_Javascript_Node.js_Architecture_Constructor_Promise - Fatal编程技术网

Javascript 让构造函数返回一个承诺是一种不好的做法吗?

Javascript 让构造函数返回一个承诺是一种不好的做法吗?,javascript,node.js,architecture,constructor,promise,Javascript,Node.js,Architecture,Constructor,Promise,我正在尝试为博客平台创建一个构造函数,它内部有许多异步操作。这些功能包括从目录中获取帖子、解析帖子、通过模板引擎发送帖子等 所以我的问题是,让我的构造函数返回一个承诺而不是他们调用的new函数的对象是不明智的 例如: var engine = new Engine({path: '/path/to/posts'}).then(function (eng) { // allow user to interact with the newly created engine object ins

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

所以我的问题是,让我的构造函数返回一个承诺而不是他们调用的
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();
});
现在,用户还可以提供补充承诺链链接:

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

// ERROR
// engine will not be available as an Engine object here
这可能会造成问题,因为用户可能会感到困惑,为什么
引擎在构建后不可用

在构造函数中使用承诺的原因是有道理的。我希望整个博客在建设阶段之后能够正常运行。然而,调用
new
后,几乎无法立即访问对象,这似乎是一种气味

我已经讨论过如何使用
engine.start().then()
engine.init()
之类的东西,它们将返回承诺。但这些看起来也很臭


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

是的,这是一个糟糕的做法。构造函数应该返回其类的一个实例,而不是其他。否则会把遗产搞砸

此外,构造函数应该只创建和初始化一个新实例。它应该设置数据结构和所有特定于实例的属性,但不执行任何任务。如果可能的话,它应该是一种没有副作用的药物,具有所有的益处

如果我想从我的构造函数中执行一些东西呢

这应该是你们班的一种方法。你想改变全局状态吗?然后显式调用该过程,而不是作为生成对象的副作用。此调用可以在实例化之后立即执行:

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

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

问问你自己:你真的需要没有数据的实例吗?你能用它吗

如果答案是否定的,那么您不应该在获得数据之前创建它。将数据本身作为构造函数的参数,而不是告诉构造函数如何获取数据(或传递数据承诺)

然后,使用静态方法加载数据,并从中返回承诺。然后链接一个调用,该调用将数据包装到一个新实例中:

Engine.load({path: '/path/to/posts'}).then(function(posts) {
    new Engine(posts).displayPosts();
});
这使得获取数据的方式具有更大的灵活性,并大大简化了构造函数。类似地,您可以编写静态工厂函数,为
引擎
实例返回承诺:

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

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

不要从构造函数返回承诺,而是将其放入
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()
这是因为您可以向一个承诺注册多个回调,它们可以在承诺解析后运行,也可以在附加回调时运行(如果已经解析)

这就是工作原理,只是它实际上没有使用承诺


Edit:自从我写了那个回复后,我就爱上了ES6/7语法,所以还有另一个例子使用它。今天你可以和巴贝尔一起使用

class Engine {

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

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

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

}

编辑:您可以将此模式与节点7和
--harmony
标志一起本机使用

为避免问题分离,请使用工厂创建对象

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

构造函数的返回值替换了新操作符刚刚生成的对象,因此返回承诺不是一个好主意。以前,构造函数的显式返回值用于singleton模式

ECMAScript 2017中更好的方法是使用静态方法:您有一个过程,即静态的数字性

构造函数之后要在新对象上运行的方法可能只有类本身知道。要将其封装在类中,可以使用process.nextTick或Promise.resolve,推迟进一步的执行,以便在构造函数的调用程序process.launch中添加侦听器和其他内容

由于几乎所有代码都是在承诺内执行的,因此错误最终会出现在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)

这是在typescript中,但应该可以轻松地转换为ECMAscript

导出类缓存{
私人承诺:承诺;
私人承诺:承诺;
构造函数(){
this.apropise=新的承诺(…);
this.b Promise=新承诺(…);
}
公共异步存储文件:Promise{
const aObject=等待这个;
// ...
}
}
一般的模式是使用构造函数将承诺存储为内部变量,并
等待方法中的承诺,并使方法都返回承诺。这允许您使用
async
/
wait
来避免长承诺链

我给出的示例对于短承诺来说已经足够好了,但是加入一些需要长承诺链的东西会使这变得很混乱,因此为了避免这种情况,请创建一个私有的
async
方法,该方法将由构造函数调用

导出类缓存{
私人承诺:承诺;
私人建议:专业
import { Injectable } from "@angular/core";
import { DirectoryEntry, File } from "@ionic-native/file/ngx";

@Injectable({
    providedIn: "root"
})
export class Cache {
    private imageCacheDirectoryPromise: Promise<DirectoryEntry>;
    private pdfCacheDirectoryPromise: Promise<DirectoryEntry>;

    constructor(
        private file: File
    ) {
        this.imageCacheDirectoryPromise = this.initDirectoryEntry("image-cache");
        this.pdfCacheDirectoryPromise = this.initDirectoryEntry("pdf-cache");
    }

    private async initDirectoryEntry(cacheDirectoryName: string): Promise<DirectoryEntry> {
        const cacheDirectoryEntry = await this.resolveLocalFileSystemDirectory(this.file.cacheDirectory);
        return this.file.getDirectory(cacheDirectoryEntry as DirectoryEntry, cacheDirectoryName, { create: true })
    }

    private async resolveLocalFileSystemDirectory(path: string): Promise<DirectoryEntry> {
        const entry = await this.file.resolveLocalFilesystemUrl(path);
        if (!entry.isDirectory) {
            throw new Error(`${path} is not a directory`)
        } else {
            return entry as DirectoryEntry;
        }
    }

    public async imageCacheDirectory() {
        return this.imageCacheDirectoryPromise;
    }

    public async pdfCacheDirectory() {
        return this.pdfCacheDirectoryPromise;
    }

}