programing

비동기 컨스트럭터가 TypeScript에서 기능합니까?

yellowcard 2023. 3. 21. 21:50
반응형

비동기 컨스트럭터가 TypeScript에서 기능합니까?

컨스트럭터 중에 필요한 설정이 있지만 허용되지 않는 것 같습니다.

비동기 Const 없음

즉, 사용할 수 없습니다.

기다리다

또 어떻게 하면 좋을까요?

지금 밖에 이런 게 있는데, 이건 내가 원하는 순서대로 안 되는 건가요?

async function run() {
  let topic;
  debug("new TopicsModel");
  try {
    topic = new TopicsModel();
  } catch (err) {
    debug("err", err);
  }

  await topic.setup();

생성자는 자신이 '구성'하는 클래스의 인스턴스를 반환해야 합니다. 때문에 다시 수 없습니다.Promise<...>기다리고 있겠습니다.

다음과 같은 작업을 수행할 수 있습니다.

  1. 셋업을 합니다.async.

  2. 생성자에서 호출하지 마십시오.

  3. 오브젝트 구축을 '완료'하고 싶을 때마다 호출합니다.

    async function run() 
    {
        let topic;
        debug("new TopicsModel");
        try 
        {
            topic = new TopicsModel();
            await topic.setup();
        } 
        catch (err) 
        {
            debug("err", err);
        }
    }
    

준비설계 패턴

약속에 목표를 넣지 말고 약속에 목표를 넣어라.

준비는 개체의 속성입니다.그래서 그것을 그 물건의 속성으로 만들어라.

승인된 답변에 설명된 대기 가능한 초기화 방법에는 심각한 제한이 있습니다.「」를 사용합니다.await이는 하나의 코드 블록만이 준비 중인 오브젝트에 암묵적으로 의존할 수 있음을 의미합니다.이는 선형 실행이 보장되는 코드에는 적합하지만 멀티 스레드 또는 이벤트 기반 코드에서는 유지할 수 없습니다.

작업/약속을 캡처하여 기다릴 수 있지만, 어떻게 하면 이에 의존하는 모든 컨텍스트에서 이를 사용할 수 있게 할 수 있을까요?

그 문제는 틀을 올바르게 짜면 더 다루기 쉽다.목표는 구축 대기 시간이 아니라 생성된 객체가 준비될 까지 대기하는 것입니다.이 두 가지는 완전히 다른 것이다.데이터베이스 접속 오브젝트 등이 Ready 상태가 되어 다시 Ready 상태로 돌아간 후 Ready 상태가 될 수도 있습니다.

컨스트럭터가 돌아왔을 때 완료되지 않을 수 있는 액티비티에 따라 준비가 완료되었는지 어떻게 판단합니까?아주 명백하게 준비는 그 물체의 특성이다.많은 프레임워크가 준비의 개념을 직접적으로 표현합니다.에는 JavaScript가 .Promise에는 C#가 Task둘 다 속성에 합니다. 둘 다 오브젝트 속성에 대해 직접 언어를 지원합니다.

시공완료 약속을 시공 객체의 속성으로 노출합니다.건설의 비동기 부분이 완료되면 약속이 해결됩니다.

.then(...)는 약속이 해결되기 전 또는 후에 실행됩니다.에는, 「이러다」, 「이러다」를 호출하는 .then이미 해결된 약속에 따라 핸들러가 즉시 실행됩니다.

class Foo {
  public Ready: Promise.IThenable<any>;
  constructor() {
    ...
    this.Ready = new Promise((resolve, reject) => {
      $.ajax(...).then(result => {
        // use result
        resolve(undefined);
      }).fail(reject);
    });
  }
}

var foo = new Foo();
foo.Ready.then(() => {
  // do stuff that needs foo to be ready, eg apply bindings
});
// keep going with other stuff that doesn't need to wait for foo

// using await
// code that doesn't need foo to be ready
await foo.Ready;
// code that needs foo to be ready

★★★★resolve(undefined);resolve();ES6 の es es es es es이치노

「」를 사용합니다.await

에서는, 이을, 이 해결법을, 「부터」, 「아까부터」, 「아까부터」, 「아까부터」, 「아까부터」, 「아까부터」, 「부터」, 「아까부터」라고 코멘트를 하고 있었습니다.await질문에 더 직접적으로 답변할 수 있습니다.

하시면 됩니다.awaitReady위의 예시와 같이 속성을 지정합니다.는 별로 않는다await종속성에 따라 코드를 분할해야 하기 때문입니다. 뒤에 .await그그 、 그에에에앞립 립립립립 。이로 인해 코드의 의도가 불분명해질 수 있습니다.

나는 사람들이 콜백의 관점에서 생각하도록 장려한다.이러한 문제를 정신적으로 구성하는 것은 C와 같은 언어들과 더 잘 호환됩니다.입출력 완료에 사용된 패턴에서 파생된 것이 확실합니다.

공장 패턴에 비해 강제성이 결여되어 있다

한 펀터는 "공장 기능이 없으면 준비 상태를 점검해야 하는 불변성을 강제할 수 없기 때문에 이 패턴은 좋지 않은 아이디어라고 생각합니다.이것은 고객에게 맡겨져 있기 때문에, 실제로 때때로 문제가 발생하는 것을 보증할 수 있습니다.」

만약 당신이 이 입장을 취한다면 사람들이 검사를 시행하지 않는 공장 방법을 구축하는 것을 어떻게 막을 수 있을까요?당신은 어디에 선을 그어야 합니까?예를 들어, 0 제수를 통과시키는 것을 막을 수 있는 것은 아무것도 없기 때문에, 나눗셈 연산자를 금지할 수 있습니까?엄연한 사실은 도메인 고유의 코드와 프레임워크 코드의 차이점을 학습하고 상식에 맞게 서로 다른 표준을 적용해야 한다는 것입니다.


선행 요소

이것은 나의 독창적인 작품이다.외부 공장이나 다른 대책에 만족하지 못해 이 디자인 패턴을 고안했습니다.한참을 찾아봤지만 해결책을 위한 선행 기술을 찾지 못했기 때문에 논란이 될 때까지 이 패턴의 창시자라고 주장합니다.

그럼에도 불구하고, 2020년에 나는 Stephen Cleary가 2013년에 이 문제에 대해 매우 유사한 해결책을 발표한 것을 발견했다.제 작업을 통해 되돌아보면, 이 접근법의 첫 번째 흔적은 제가 거의 동시에 작업한 코드에 나타나 있습니다.클리어리가 먼저 모든 것을 정리했을 것으로 생각되지만, 그는 디자인 패턴으로 공식화하거나 문제를 안고 있는 다른 사람들이 쉽게 찾을 수 있는 곳에 공개하지 않았습니다.또한 Cleary는 Readiness 패턴(아래 참조)의 한 가지 응용 프로그램인 건설만을 취급합니다.

요약

패턴은

  • 그것이 기술하는 목적어에 약속을 넣다
  • 을 재산으로 밝히다Ready
  • 항상 Ready 속성을 통해 약속을 참조합니다(클라이언트 코드 변수에 약속을 캡처하지 마십시오).

이것은 명확한 단순한 의미론을 확립하고 다음을 보증한다.

  • 약속이 생성되고 관리될 것이다.
  • 약속은 그것이 설명하는 목적과 동일한 범위를 가진다.
  • 준비 의존의 의미는 클라이언트 코드에서 두드러지고 명확하다.
  • 치환된 를 들어 네트워크되지 않은 경우 됨) 는 이를 합니다.thing.Ready의 약속을 합니다.

패턴을 사용하여 오브젝트가 자신의 약속을 관리할 때까지 이 마지막 것은 악몽입니다.약속을 변수로 포착하는 것을 자제하는 것도 좋은 이유입니다.

일부 개체에는 일시적으로 비활성 상태가 되는 메서드가 있으며 패턴은 수정 없이 해당 시나리오에서 사용될 수 있습니다. ★★★★★★★★★★★★★★★★★★★」obj.Ready.then(...) 님의 약속 속성이 되는 경우 되는 약속 합니다.Ready오브젝트 상태를 무효화하려는 액션이 있을 때마다 새로운 약속을 작성할 수 있습니다.

클로징 노트

Readiness 패턴은 구축에만 국한된 것이 아닙니다.이것은 건설에 쉽게 적용될 수 있지만 주정부의 의존성을 충족시키는 것에 관한 것입니다.비동기 코드의 시대에는 시스템이 필요하며, 약속의 단순한 선언적 의미론 때문에 가능한 한 빨리 조치를 취해야 한다는 생각을 쉽게 표현할 수 있습니다.이러한 용어로 프레이밍을 시작하면 롱런 메서드 또는 컨스트럭터에 대한 논쟁은 무의미해집니다.

지연된 초기화는 여전히 유효합니다. 앞서 언급했듯이 준비와 느린 부하를 결합할 수 있습니다.하지만 오브젝트를 사용하지 않을 가능성이 높다면 왜 일찍 만들까요?온 디맨드로 작성하는 것이 좋을지도 모릅니다.그렇지 않을 수도 있다; 때때로 당신은 필요와 성취의 인식 사이의 지연을 참을 수 없다.

고양이 가죽을 벗기는 방법은 한 가지가 아닙니다.임베디드 소프트웨어를 작성할 때는 자원 풀을 포함한 모든 것을 미리 작성합니다.이로 인해 리크가 불가능해지고 컴파일 시에 메모리 요구량을 알 수 있습니다.하지만 이는 폐쇄적인 문제 공간에 대한 해결책일 뿐입니다.

대신 비동기 공장 방식을 사용하십시오.

class MyClass {
   private mMember: Something;

   constructor() {
      this.mMember = await SomeFunctionAsync(); // error
   }
}

이하가 됩니다.

class MyClass {
   private mMember: Something;

   // make private if possible; I can't in TS 1.8
   constructor() {
   }

   public static CreateAsync = async () => {
      const me = new MyClass();
      
      me.mMember = await SomeFunctionAsync();

      return me;
   };
}

이것은 당신이 이러한 종류의 물건의 구축을 기다려야 한다는 것을 의미하지만, 그것은 당신이 그것들을 만들기 위해 무엇인가를 기다려야 하는 상황에 있다는 것을 이미 암시해야 한다.

다른 방법이 있지만 좋은 생각은 아닌 것 같습니다.

// probably BAD
class MyClass {
   private mMember: Something;

   constructor() {
      this.LoadAsync();
   }

   private LoadAsync = async () => {
      this.mMember = await SomeFunctionAsync();
   };
}

이것은 동작할 수 있어 실제로 문제가 발생한 적은 없지만, 실제로 사용하기 시작하면 오브젝트가 완전히 초기화되지 않기 때문에 위험한 것 같습니다.

다른 방법으로는 첫 번째 옵션보다 더 나은 방법이 있을 수 있습니다. 부품을 기다린 다음 다음 개체를 구성하는 것입니다.

export class MyClass {
   private constructor(
      private readonly mSomething: Something,
      private readonly mSomethingElse: SomethingElse
   ) {
   }

   public static CreateAsync = async () => {
      const something = await SomeFunctionAsync();
      const somethingElse = await SomeOtherFunctionAsync();

      return new MyClass(something, somethingElse);
   };
}

제가 찾은 해결책은

export class SomeClass {
  private initialization;

  // Implement async constructor
  constructor() {
    this.initialization = this.init();
  }

  async init() {
    await someAsyncCall();
  }

  async fooMethod() {
    await this.initialization();
    // ...some other stuff
  }

  async barMethod() {
    await this.initialization();
    // ...some other stuff
  }

비동기/대기 권한을 부여하는 약속은 동일한 값으로 여러 번 해결할 수 있기 때문에 작동합니다.

꽤 오래되었다는 것은 알지만, 또 다른 옵션은 오브젝트를 만들고 초기화를 기다리는 팩토리를 갖는 것입니다.

// Declare the class
class A {

  // Declare class constructor
  constructor() {

    // We didn't finish the async job yet
    this.initialized = false;

    // Simulates async job, it takes 5 seconds to have it done
    setTimeout(() => {
      this.initialized = true;
    }, 5000);
  }

  // do something usefull here - thats a normal method
  useful() {
    // but only if initialization was OK
    if (this.initialized) {
      console.log("I am doing something useful here")

    // otherwise throw an error which will be caught by the promise catch
    } else {
      throw new Error("I am not initialized!");
    }
  }

}

// factory for common, extensible class - that's the reason for the constructor parameter
// it can be more sophisticated and accept also params for constructor and pass them there
// also, the timeout is just an example, it will wait for about 10s (1000 x 10ms iterations
function factory(construct) {

  // create a promise
  var aPromise = new Promise(
    function(resolve, reject) {

      // construct the object here
      var a = new construct();

      // setup simple timeout
      var timeout = 1000;

      // called in 10ms intervals to check if the object is initialized
      function waiter() {
    
        if (a.initialized) {
          // if initialized, resolve the promise
          resolve(a);
        } else {

          // check for timeout - do another iteration after 10ms or throw exception
          if (timeout > 0) {     
            timeout--;
            setTimeout(waiter, 10);            
          } else {            
            throw new Error("Timeout!");            
          }

        }
      }
  
      // call the waiter, it will return almost immediately
      waiter();
    }
  );

  // return promise of the object being created and initialized
  return a Promise;
}


// this is some async function to create object of A class and do something with it
async function createObjectAndDoSomethingUseful() {

  // try/catch to capture exceptions during async execution
  try {
    // create object and wait until its initialized (promise resolved)
    var a = await factory(A);
    // then do something usefull
    a.useful();
  } catch(e) {
    // if class instantiation failed from whatever reason, timeout occured or useful was called before the object finished its initialization
    console.error(e);
  }

}

// now, perform the action we want
createObjectAndDoSomethingUsefull();

// spaghetti code is done here, but async probably still runs

개인 컨스트럭터와 정적 공장 방식 FTW를 사용합니다.클라이언트로부터 캡슐화되어 있는 검증 로직 또는 데이터 강화를 강제하는 가장 좋은 방법입니다.

class Topic {
  public static async create(id: string): Promise<Topic> {
    const topic = new Topic(id);
    await topic.populate();
    return topic;
  }

  private constructor(private id: string) {
    // ...
  }

  private async populate(): Promise<void> {
    // Do something async. Access `this.id` and any other instance fields
  }
}

// To instantiate a Topic
const topic = await Topic.create();

당신은 그 방정식에서 완전히 제외하는 것을 선택할 수 있다.필요한 경우 컨스트럭터에서 호출할 수 있습니다.주의할 점은 컨스트럭터가 아닌 셋업/초기화 함수로 반환값을 처리해야 한다는 것입니다.

각도 1.6.3을 사용하면 됩니다.

import { module } from "angular";
import * as R from "ramda";
import cs = require("./checkListService");

export class CheckListController {

    static $inject = ["$log", "$location", "ICheckListService"];
    checkListId: string;

    constructor(
        public $log: ng.ILogService,
        public $loc: ng.ILocationService,
        public checkListService: cs.ICheckListService) {
        this.initialise();
    }

    /**
     * initialise the controller component.
     */
    async initialise() {
        try {
            var list = await this.checkListService.loadCheckLists();
            this.checkListId = R.head(list).id.toString();
            this.$log.info(`set check list id to ${this.checkListId}`);
         } catch (error) {
            // deal with problems here.
         }
    }
}

module("app").controller("checkListController", CheckListController)

인스턴스를 반환하는 설정 비동기 메서드를 사용합니다.

FooSessionParams 개체에서 fooSession을 만드는 것이 비동기 함수임을 알고 'FooSession' 클래스의 인스턴스 또는 'fooSessionParams' 개체를 사용하여 'Foo' 클래스를 설치하는 방법도 이와 유사한 문제가 있었습니다.다음 중 하나를 수행하여 설치하려고 했습니다.

let foo = new Foo(fooSession);

또는

let foo = await new Foo(fooSessionParams);

그리고 두 가지 용도가 너무 다르기 때문에 공장을 원하지 않았다.그러나 아시다시피 컨스트럭터로부터의 약속은 반환할 수 없습니다(반환 서명이 다릅니다).이렇게 해결했습니다.

class Foo {
    private fooSession: FooSession;

    constructor(fooSession?: FooSession) {
        if (fooSession) {
            this.fooSession = fooSession;
        }
    }

    async setup(fooSessionParams: FooSessionParams): Promise<Foo> {
        this.fooSession = await getAFooSession(fooSessionParams);
        return this;
    }
}

대상 부분은 setup async 메서드가 인스턴스 자체를 반환하는 부분입니다.FooSession' 인스턴스가 있는 경우 다음과 같이 사용할 수 있습니다.

let foo = new Foo(fooSession);

FooSession 인스턴스가 없는 경우 다음 중 하나의 방법으로 Foo를 설정할 수 있습니다.

let foo = await new Foo().setup(fooSessionParams);

(처음에는 내가 원하던 것에 가깝기 때문에 마녀는 내가 선호하는 방법이다.

let foo = new Foo();
await foo.setup(fooSessionParams);

대체 방법으로 다음과 같은 정적 방법을 추가할 수도 있습니다.

    static async getASession(fooSessionParams: FooSessionParams): FooSession {
        let fooSession: FooSession = await getAFooSession(fooSessionParams);
        return fooSession;
    }

다음과 같이 설치합니다.

let foo = new Foo(await Foo.getASession(fooSessionParams));

주로 스타일의 문제입니다.

약속 상태에 대한 홀더 만들기:

class MyClass {
    constructor(){
        this.#fetchResolved = this.fetch()
    }
    #fetchResolved: Promise<void>;
    fetch = async (): Promise<void> => {
        return new Promise(resolve => resolve()) // save data to class property or simply add it by resolve() to #fetchResolved reference
    }
    isConstructorDone = async (): boolean => {
        await this.#fetchResolved;
        return true; // or any other data depending on constructor finish the job
    }
}

사용 방법:

const data = new MyClass();
const field = await data.isConstructorDone();

공장을 이용하다.이런 경우엔 그게 가장 좋은 방법이에요

문제는 특히 상속의 경우 공장 패턴의 타입 스크립트유형을 정의하는 것이 까다롭다는 점입니다.

이제 Typescript에서 적절하게 구현하는 방법을 살펴보겠습니다.

상속 없음

클래스 상속이 필요하지 않은 경우 패턴은 다음과 같습니다.

class Person {
  constructor(public name: string) {}

  static async Create(name: string): Promise<Person> {
    const instance = new Person(name);

    /** Your async code here! **/

    return instance;
  }
}

const person = await Person.Create('John');

클래스 상속

만약 당신이 수업을 연장해야 한다면, 당신은 문제에 직면할 것입니다.Create방법

유형 스크립트에서 일반 클래스로 이 문제를 해결할 수 있습니다.

type PersonConstructor<T = {}> = new (...args: any[]) => T;

class Person {
  constructor(public name: string) {}

  static async Create<T extends Person>(
    this: PersonConstructor<T>,
    name: string,
    ...args: any[]
  ): Promise<T> {
    const instance = new this(name, ...args);

    /** Your async code here! **/

    return instance;
  }
}
class MyPerson extends Person {
  constructor(name: string, public lastName: string) {
    super(name);
  }
}

const myPerson = await MyPerson.Create('John', 'Snow');

공장 증설

「 」를 할 수 .Create메서드도 있습니다.

class MyPerson extends Person {
  constructor(name: string, public lastName: string) {
    super(name);
  }

  static async Create<T extends Person>(
    this: PersonConstructor<T>,
    name: string,
    lastName: string,
    ...args: any[]
  ): Promise<T> {
    const instance = await super.Create(name, lastName, ...args);

    /** Your async code here! **/

    return instance as T;
  }
}

const myPerson = await MyPerson.Create('John', 'Snow');

보다 상세한 대안

방식으로 수 방식은 Class 로 하지 범용 클래스Create★★★★★★ 。

type PersonConstructor<T = {}> = new (...args: any[]) => T;

class Person {
  constructor(public name: string) {}

  protected async init(): Promise<void> {
    /** Your async code here! **/
    // this.name = await ...
  }

  static async Create<T extends Person>(
    this: PersonConstructor<T>,
    name: string,
    ...args: any[]
  ): Promise<T> {
    const instance = new this(name, ...args);

    await instance.init();

    return instance;
  }
}
class MyPerson extends Person {
  constructor(name: string, public lastName: string) {
    super(name);
  }

  override async init(): Promise<void> {
    await super.init();

    /** Your async code here! **/
    // this.lastName = await ...
  }
}

const myPerson = await MyPerson.Create('John', 'Snow');

정적인 방법은 나쁜 관행 아닌가요?

네, 단 한 가지 예외는 공장입니다.

왜 건설업자에게 약속을 돌려주지 않는가?

그렇게 할 수 있지만, 컨스트럭터가 다음과 같이 되어 있기 때문에 많은 사람들이 코드를 나쁜 패턴으로 간주합니다.

  • 클래스 유형반복)을 해야 합니다.Promise<Person>Person
  • 비동기 코드를 실행해서는 안 됩니다.

또는 설정을 지나치게 복잡하게 만들지 않고 실제 비동기 모델을 고수할 수도 있습니다. 10배 중 9배는 비동기식 설계와 동기식 설계로 귀결됩니다.예를 들어 컨스트럭터의 약속 콜백에서 상태 변수를 초기화하는 것과 동일한 것이 필요한 React 컴포넌트가 있습니다.null 데이터 예외를 회피하기 위해 필요한 것은 빈 상태 오브젝트를 설정하고 비동기 콜백으로 설정하는 것뿐이었습니다.예를 들어, Firebase에서 약속과 콜백이 반환된 내용을 읽어보십시오.

        this._firebaseService = new FirebaseService();
        this.state = {data: [], latestAuthor: '', latestComment: ''};

        this._firebaseService.read("/comments")
        .then((data) => {
            const dataObj = data.val();
            const fetchedComments = dataObj.map((e: any) => {
                return {author: e.author, text: e.text}
            });

            this.state = {data: fetchedComments, latestAuthor: '', latestComment: ''};

        });

콜백 전에 디폴트(빈 객체 및 빈 문자열)로 상태가 설정되기 때문에 이 방법을 사용하면 코드는 늘 예외를 제외하고 컴포넌트에 영향을 주지 않고 AJAX 동작을 유지합니다.사용자에게 잠시 빈 목록이 표시될 수 있지만, 그 목록은 빠르게 채워집니다.데이터가 로드되는 동안 스피너를 적용하는 것이 좋습니다.나는 종종 이 투고에서와 같이 너무 복잡한 일을 제안하는 개인들에 대해 듣지만 원래의 흐름은 재검토되어야 한다.

언급URL : https://stackoverflow.com/questions/35743426/async-constructor-functions-in-typescript

반응형