programing

컨스트럭터 기능이 약속을 반환하도록 하는 것은 나쁜 관행입니까?

javamemo 2023. 9. 12. 19:47
반응형

컨스트럭터 기능이 약속을 반환하도록 하는 것은 나쁜 관행입니까?

블로그 플랫폼을 위한 컨스트럭터를 만들려고 하는데 내부에서 많은 비동기 작업이 진행되고 있습니다.이는 디렉토리에서 게시물을 캡처하는 것, 구문 분석하는 것, 템플릿 엔진을 통해 전송하는 것 등 다양합니다.

제 은 이 한 가 의 을 하도록 이 하지 일까 입니다 일까 하지 이 은 하도록 을 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

이는 사용자가 그 이유를 혼동할 수 있기 때문에 문제를 일으킬 수 있습니다. engine 시공 후에는 사용할 수 없습니다.

생성자에서 약속을 사용해야 하는 이유는 타당합니다.저는 공사 단계 이후에 블로그 전체가 기능하기를 원합니다.하지만 전화를 걸면 바로 물건에 접근할 수 없을 정도로 냄새가 나는 것 같습니다.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();
});

이를 통해 데이터를 획득하는 방법이 훨씬 더 유연해지고, 생성자가 훨씬 단순해집니다.로,은는적장를할다수을r다수sstyn에 대한 약속을 반환하는 정적 공장 함수를 할 수 있습니다.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);
    });
});

저도 같은 문제에 부딪혀 이 간단한 해결책을 생각해 냈습니다.

작성자로부터 약속을 반환하는 대신 입력합니다.this._initialized다음과 같은 속성:

function Engine(path) {
  this._initialized = Promise.resolve()
    .then(() => {
      return doSomethingAsync(path)
    })
    .then((result) => {
      this.resultOfAsyncOp = result
    })
}
  

그런 다음 초기화 후 실행되는 콜백에 모든 메서드를 랩핑합니다.

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

API 소비자 관점에서 본 모습:

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

이것은 약속에 여러 콜백을 등록할 수 있으며 콜백이 해결된 후 또는 이미 해결된 경우 콜백을 연결할 때 실행되기 때문에 작동합니다.

몽고스킨은 실제로 약속을 사용하지 않는 것을 제외하고는 이렇게 작동합니다.


편집: 그 답장을 쓴 이후로 ES6/7 구문에 푹 빠져서 사용한 예가 또 있습니다.

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

  async _initialize() {
    // actual async constructor logic
    this.resultOfAsyncOp = await doSomethingAsync(path)
  }

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

관심사가 분리되지 않도록 하려면 공장을 사용하여 개체를 만듭니다.

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

생성자로부터의 반환 값은 새로운 연산자가 방금 생성한 객체를 대체하므로 약속을 반환하는 것은 좋은 생각이 아닙니다.싱글톤 패턴은 기존에 생성자의 명시적인 반환 값이 사용되었습니다.

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)

이것은 타이프스크립트로 되어 있지만 ECMA스크립트로 쉽게 변환되어야 합니다.

export class Cache {
    private aPromise: Promise<X>;
    private bPromise: Promise<Y>;
    constructor() {
        this.aPromise = new Promise(...);
        this.bPromise = new Promise(...);
    }
    public async saveFile: Promise<DirectoryEntry> {
        const aObject = await this.aPromise;
        // ...
        
    }
}

일반적인 패턴은 컨스트럭터를 사용하여 약속을 내부 변수로 저장하는 것입니다.await메소드에 있는 약속과 메소드를 모두 반환 약속으로 만듭니다.이것은 당신이 사용할 수 있습니다.async/await긴 약속의 사슬을 피하기 위해서입니다.

제가 예를 든 것은 짧은 약속으로도 충분하지만, 긴 약속 사슬이 필요한 것을 넣는 것은 이것을 지저분하게 만들 것이므로, 그것을 피하기 위해 비공개를 만듭니다.async생성자가 호출할 메서드입니다.

export class Cache {
    private aPromise: Promise<X>;
    private bPromise: Promise<Y>;
    constructor() {
        this.aPromise = initAsync();
        this.bPromise = new Promise(...);
    }
    public async saveFile: Promise<DirectoryEntry> {
        const aObject = await this.aPromise;
        // ...
        
    }
    private async initAsync() : Promise<X> {
        // ...
    }

}

Ionic/Angular에 대한 좀 더 구체적인 예시가 있습니다.

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

}

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

반응형