IT story

생성자 함수가 Promise를 반환하도록하는 것은 나쁜 습관입니까?

hot-time 2020. 6. 15. 08:12
반응형

생성자 함수가 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();
});

이제 사용자는 보충 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()했습니다. 그러나 그들은 또한 냄새가 나는 것처럼 보입니다.

편집 : 이것은 Node.js 프로젝트에 있습니다.


그렇습니다, 나쁜 연습입니다. 생성자는 클래스의 인스턴스를 반환해야합니다. 그것은 엉망 것 new연산자를 달리 상속.

또한 생성자는 새 인스턴스 만 만들고 초기화해야합니다. 데이터 구조와 모든 인스턴스 별 속성을 설정해야하지만 작업을 실행 해서는 안됩니다. 가능한 모든 부작용과 함께 가능한 경우 부작용이없는 순수한 기능 이어야합니다 .

생성자에서 작업을 실행하려면 어떻게합니까?

그것은 당신의 수업의 방법으로 가야합니다. 전역 상태를 변경하고 싶습니까? 그런 다음 객체 생성의 부작용이 아닌 해당 프로 시저를 명시 적으로 호출하십시오. 이 호출은 인스턴스화 직후에 진행될 수 있습니다.

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

해당 작업이 비동기 인 경우 이제 메서드에서 결과에 대한 약속을 쉽게 반환하여 완료 될 때까지 쉽게 기다릴 수 있습니다.
그러나 메소드가 (비동기 적으로) 인스턴스를 변경하고 다른 메소드가 그 인스턴스에 의존 할 때 (실제로 동 기적 인 경우에도 비동기가 됨) 결과를 얻었을 때이 패턴을 권장하지 않습니다. 일부 내부 큐 관리가 진행 중입니다. 존재하도록 인스턴스를 코딩하지 말고 실제로는 사용할 수 없습니다.

비동기 적으로 인스턴스에 데이터를로드하려면 어떻게합니까?

스스로에게 물어보십시오 : 실제로 데이터가없는 인스턴스가 필요합니까? 어떻게 든 사용할 수 있습니까?

이에 대한 대답이 No 이면 데이터를 갖기 전에 작성하지 않아야합니다. 생성자에게 데이터를 가져 오는 방법을 알려주거나 데이터에 대한 약속을 전달하는 대신 데이터 자체를 생성자에 대한 매개 변수로 만듭니다.

그런 다음 정적 메소드를 사용하여 약속을 리턴하는 데이터를로드하십시오. 그런 다음 데이터를 새 인스턴스로 래핑하는 호출을 연결하십시오.

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

이를 통해 데이터를 수집하는 방식이 훨씬 유연 해지며 생성자가 크게 단순화됩니다. 마찬가지로 Engine인스턴스에 대한 약속을 반환하는 정적 팩토리 함수를 작성할 수 있습니다 .

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

나는 같은 문제가 발생 하여이 간단한 해결책을 생각해 냈습니다.

생성자로부터 Promise를 반환하는 대신 this.initialization다음과 같이 속성에 넣습니다 .

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

Then, wrap every method in a callback that runs after the initialization, like that:

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

How it looks from the API consumer perspective:

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

This works because you can register multiple callbacks to a promise and they run either after it resolves or, if it's already resolved, at the time of attaching the callback.

This is how mongoskin works, except it doesn't actually use promises.


Edit: Since I wrote that reply I've fallen in love with ES6/7 syntax so there's another example using that. You can use it today with 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: You can use this pattern natively with node 7 and --harmony flag!


To avoid the separation of concerns, use a factory to create the object.

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

The return value from the constructor replaces the object that the new operator just produced, so returning a promise is not a good idea. Previously, an explicit return value from the constructor was used for the singleton pattern.

The better way in ECMAScript 2017 is to use a static methods: you have one process, which is the numerality of static.

Which method to be run on the new object after the constructor may be known only to the class itself. To encapsulate this inside the class, you can use process.nextTick or Promise.resolve, postponing further execution allowing for listeners to be added and other things in Process.launch, the invoker of the constructor.

Since almost all code executes inside of a Promise, errors will end up in Process.fatal

This basic idea can be modified to fit specific encapsulation needs.

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)

참고URL : https://stackoverflow.com/questions/24398699/is-it-bad-practice-to-have-a-constructor-function-return-a-promise

반응형