Async / Await Class Con constructor


169

Hiện tại, tôi đang cố gắng sử dụng async/awaittrong một hàm xây dựng lớp. Điều này là để tôi có thể nhận được một e-mailthẻ tùy chỉnh cho một dự án Electron mà tôi đang làm việc.

customElements.define('e-mail', class extends HTMLElement {
  async constructor() {
    super()

    let uid = this.getAttribute('data-uid')
    let message = await grabUID(uid)

    const shadowRoot = this.attachShadow({mode: 'open'})
    shadowRoot.innerHTML = `
      <div id="email">A random email message has appeared. ${message}</div>
    `
  }
})

Tuy nhiên, hiện tại, dự án không hoạt động, với lỗi sau:

Class constructor may not be an async method

Có cách nào để phá vỡ điều này để tôi có thể sử dụng async / await trong này không? Thay vì yêu cầu gọi lại hoặc .then ()?


6
Mục đích của nhà xây dựng là phân bổ cho bạn một đối tượng và sau đó trả lại ngay lập tức. Bạn có thể cụ thể hơn rất nhiều về chính xác lý do tại sao bạn nghĩ rằng nhà xây dựng của bạn nên không đồng bộ? Bởi vì chúng tôi gần như được đảm bảo xử lý một vấn đề XY ở đây.
Mike 'Pomax' Kamermans

4
@ Mike'Pomax'Kamermans Điều đó hoàn toàn có thể. Về cơ bản, tôi cần truy vấn cơ sở dữ liệu để có được siêu dữ liệu cần thiết để tải phần tử này. Truy vấn cơ sở dữ liệu là một hoạt động không đồng bộ và do đó tôi yêu cầu một số cách chờ đợi để hoàn thành việc này trước khi xây dựng phần tử. Tôi thà không sử dụng các cuộc gọi lại, vì tôi đã sử dụng await / async trong suốt phần còn lại của dự án và muốn tiếp tục.
Alexander Craggie

@ Mike'Pomax'Kamermans Toàn bộ bối cảnh của điều này là một ứng dụng email, trong đó mỗi phần tử HTML trông giống nhau <e-mail data-uid="1028"></email>và từ đó được điền thông tin bằng customElements.define()phương thức này.
Alexander Craggie

Bạn gần như không muốn một nhà xây dựng không đồng bộ. Tạo một hàm tạo đồng bộ trả về đối tượng của bạn và sau đó sử dụng một phương thức như .init()để thực hiện các công cụ không đồng bộ. Thêm vào đó, vì bạn là sublcass HTMLEuity, rất có khả năng mã sử dụng lớp này không có ý tưởng nào đó là một thứ không đồng bộ nên bạn có thể sẽ phải tìm một giải pháp hoàn toàn khác.
jfriend00

Câu trả lời:


262

Điều này không bao giờ có thể làm việc.

Các asynctừ khóa cho phép awaitđược sử dụng trong một hàm đánh dấu là asyncnhưng nó cũng chuyển đổi chức năng đó vào một máy phát điện lời hứa. Vì vậy, một chức năng được đánh dấu asyncsẽ trả lại một lời hứa. Mặt khác, một hàm tạo trả về đối tượng mà nó đang xây dựng. Do đó, chúng tôi có một tình huống mà bạn muốn trả lại một đối tượng và một lời hứa: một tình huống không thể.

Bạn chỉ có thể sử dụng async / await nơi bạn có thể sử dụng lời hứa vì về cơ bản chúng là cú pháp đường cho lời hứa. Bạn không thể sử dụng lời hứa trong hàm tạo vì hàm tạo phải trả về đối tượng sẽ được xây dựng, không phải là lời hứa.

Có hai mẫu thiết kế để khắc phục điều này, cả hai đều được phát minh trước khi những lời hứa xung quanh.

  1. Sử dụng một init()chức năng. Điều này hoạt động hơi giống với jQuery .ready(). Đối tượng bạn tạo chỉ có thể được sử dụng bên trong chức năng inithoặc của chính readynó:

    Sử dụng:

    var myObj = new myClass();
    myObj.init(function() {
        // inside here you can use myObj
    });
    

    Thực hiện:

    class myClass {
        constructor () {
    
        }
    
        init (callback) {
            // do something async and call the callback:
            callback.bind(this)();
        }
    }
    
  2. Sử dụng một người xây dựng. Tôi chưa thấy điều này được sử dụng nhiều trong javascript nhưng đây là một trong những công việc phổ biến hơn trong Java khi một đối tượng cần được xây dựng không đồng bộ. Tất nhiên, mẫu trình xây dựng được sử dụng khi xây dựng một đối tượng đòi hỏi nhiều tham số phức tạp. Đó chính xác là trường hợp sử dụng cho các nhà xây dựng không đồng bộ. Sự khác biệt là trình xây dựng async không trả về một đối tượng mà là một lời hứa của đối tượng đó:

    Sử dụng:

    myClass.build().then(function(myObj) {
        // myObj is returned by the promise, 
        // not by the constructor
        // or builder
    });
    
    // with async/await:
    
    async function foo () {
        var myObj = await myClass.build();
    }
    

    Thực hiện:

    class myClass {
        constructor (async_param) {
            if (typeof async_param === 'undefined') {
                throw new Error('Cannot be called directly');
            }
        }
    
        static build () {
            return doSomeAsyncStuff()
               .then(function(async_result){
                   return new myClass(async_result);
               });
        }
    }
    

    Thực hiện với async / await:

    class myClass {
        constructor (async_param) {
            if (typeof async_param === 'undefined') {
                throw new Error('Cannot be called directly');
            }
        }
    
        static async build () {
            var async_result = await doSomeAsyncStuff();
            return new myClass(async_result);
        }
    }
    

Lưu ý: mặc dù trong các ví dụ ở trên, chúng tôi sử dụng lời hứa cho trình tạo async nhưng chúng không thực sự cần thiết. Bạn có thể dễ dàng viết một trình xây dựng chấp nhận gọi lại.


Lưu ý về chức năng gọi bên trong chức năng tĩnh.

Điều này không liên quan gì đến các hàm tạo async nhưng với từ khóa thisthực sự có nghĩa là gì (có thể hơi ngạc nhiên với những người đến từ các ngôn ngữ tự động phân giải tên phương thức, nghĩa là các ngôn ngữ không cần thistừ khóa).

Các thistừ khóa đề cập đến các đối tượng khởi tạo. Không phải lớp. Do đó, bạn thường không thể sử dụng thisbên trong các hàm tĩnh vì hàm tĩnh không bị ràng buộc với bất kỳ đối tượng nào mà bị ràng buộc trực tiếp với lớp.

Điều đó có nghĩa là, trong đoạn mã sau:

class A {
    static foo () {}
}

Bạn không thể làm được:

var a = new A();
a.foo() // NOPE!!

thay vào đó bạn cần gọi nó là:

A.foo();

Do đó, đoạn mã sau sẽ dẫn đến lỗi:

class A {
    static foo () {
        this.bar(); // you are calling this as static
                    // so bar is undefinned
    }
    bar () {}
}

Để sửa nó, bạn có thể tạo barmột hàm thông thường hoặc một phương thức tĩnh:

function bar1 () {}

class A {
    static foo () {
        bar1();   // this is OK
        A.bar2(); // this is OK
    }

    static bar2 () {}
}

lưu ý rằng dựa trên các nhận xét, ý tưởng là đây là một phần tử html, thường không có hướng dẫn sử dụng init()nhưng có chức năng gắn với một số thuộc tính cụ thể như srchoặc href(và trong trường hợp này data-uid), có nghĩa là sử dụng một setter mà cả hai liên kết và khởi động init mỗi khi giá trị mới bị ràng buộc (và có thể trong quá trình xây dựng, nhưng tất nhiên không phải chờ đợi trên đường dẫn mã kết quả)
Mike 'Pomax' Kamermans

Bạn nên bình luận về lý do tại sao câu trả lời dưới đây là không đủ (nếu có). Hoặc giải quyết nó bằng cách khác.
Augie Gardner

Tôi tò mò tại sao bindđược yêu cầu trong ví dụ đầu tiên callback.bind(this)();? Vì vậy, bạn có thể làm những điều như this.otherFunc()trong cuộc gọi lại?
Alexander Craggie

1
@AlexanderCraggie Đó chỉ là sự tiện lợi để thistrong cuộc gọi lại đề cập đến myClass. Nếu bạn luôn sử dụng myObjthay vì thisbạn không cần nó
slebetman

1
Hiện tại là một hạn chế về ngôn ngữ nhưng tôi không hiểu tại sao trong tương lai bạn không thể có const a = await new A()cùng một cách chúng ta có các chức năng thông thường và chức năng không đồng bộ.
7ynk3r

138

Bạn chắc chắn có thể làm điều này. Về cơ bản:

class AsyncConstructor {
    constructor() {
        return (async () => {

            // All async code here
            this.value = await asyncFunction();

            return this; // when done
        })();
    }
}

để tạo lớp sử dụng:

let instance = await new AsyncConstructor();

Giải pháp này có một vài cú ngã ngắn:

superlưu ý : Nếu bạn cần sử dụng super, bạn không thể gọi nó trong cuộc gọi lại async.

TypeScript lưu ý: điều này gây ra sự cố với TypeScript vì hàm tạo trả về kiểu Promise<MyClass>thay vì MyClass. Không có cách dứt khoát để giải quyết điều này mà tôi biết. Một cách tiềm năng được đề xuất bởi @blitter là đặt /** @type {any} */ở phần đầu của cơ thể người xây dựng. Tôi không biết điều này có hoạt động trong mọi tình huống hay không.


1
@PAStheLoD Tôi không nghĩ rằng nó sẽ giải quyết đối tượng mà không trả lại tuy nhiên bạn đang nói rằng vì vậy tôi sẽ xem xét thông số kỹ thuật và cập nhật ...
Downgoat

2
@JuanLanus khối async sẽ tự động nắm bắt các tham số, vì vậy đối với x đối số bạn chỉ cần thực hiệnconstructor(x) { return (async()=>{await f(x); return this})() }
Downgoat

1
@PAStheLoD: return thislà cần thiết, bởi vì trong khi constructornó tự động cho bạn, thì IIFE async đó không có, và cuối cùng bạn sẽ trả lại một đối tượng trống.
Dan Dascalescu

1
Hiện tại, kể từ TS 3.5.1 nhắm mục tiêu ES5, ES2017, ES2018 (và có thể là những người khác, nhưng tôi chưa kiểm tra) nếu bạn trả lại trong một hàm tạo, bạn nhận được thông báo lỗi này: "Kiểu chữ ký của hàm tạo phải được gán cho loại thể hiện của lớp. " Loại IIFE là một Lời hứa <này> và vì lớp học không phải là Lời hứa <T>, tôi không thấy nó có thể hoạt động như thế nào. (Những gì bạn có thể trả lại ngoài 'cái này'?) Vì vậy, điều này có nghĩa là cả hai trả lại là không cần thiết. (Cái bên ngoài tệ hơn một chút, vì nó dẫn đến lỗi biên dịch.)
PAStheLoD

3
@PAStheLoD yeah, đó là một giới hạn bản in. Thông thường trong JS, một lớp Tsẽ trả về Tkhi được xây dựng nhưng để có được khả năng không đồng bộ, chúng ta trả về Promise<T>cái nào sẽ giải quyết this, nhưng điều đó gây nhầm lẫn cho bản in. Bạn không cần phải trả lại bên ngoài nếu không bạn sẽ không biết khi nào lời hứa kết thúc hoàn thành, do đó phương pháp này sẽ không hoạt động trên TypeScript (trừ khi có một số hack có lẽ là bí danh?). Không phải là một chuyên gia về bản in mặc dù vậy không thể nói điều đó
Downgoat

7

Vì các hàm async là lời hứa, bạn có thể tạo một hàm tĩnh trên lớp của mình, hàm này thực thi một hàm async trả về thể hiện của lớp:

class Yql {
  constructor () {
    // Set up your class
  }

  static init () {
    return (async function () {
      let yql = new Yql()
      // Do async stuff
      await yql.build()
      // Return instance
      return yql
    }())
  }  

  async build () {
    // Do stuff with await if needed
  }
}

async function yql () {
  // Do this instead of "new Yql()"
  let yql = await Yql.init()
  // Do stuff with yql instance
}

yql()

Gọi với let yql = await Yql.init()một chức năng không đồng bộ.


5

Dựa trên nhận xét của bạn, có lẽ bạn nên thực hiện những gì mọi HTMLEuity khác khi tải tài sản thực hiện: làm cho nhà xây dựng bắt đầu một hành động tải, tạo ra sự kiện tải hoặc lỗi tùy thuộc vào kết quả.

Vâng, điều đó có nghĩa là sử dụng lời hứa, nhưng nó cũng có nghĩa là "thực hiện mọi thứ giống như mọi yếu tố HTML khác", vì vậy bạn đang ở trong một công ty tốt. Ví dụ:

var img = new Image();
img.onload = function(evt) { ... }
img.addEventListener("load", evt => ... );
img.onerror = function(evt) { ... }
img.addEventListener("error", evt => ... );
img.src = "some url";

điều này khởi động một tải không đồng bộ của tài sản nguồn, khi nó thành công, kết thúc onloadvà khi nó sai, kết thúc onerror. Vì vậy, làm cho lớp của riêng bạn cũng làm điều này:

class EMailElement extends HTMLElement {
  constructor() {
    super();
    this.uid = this.getAttribute('data-uid');
  }

  setAttribute(name, value) {
    super.setAttribute(name, value);
    if (name === 'data-uid') {
      this.uid = value;
    }
  }

  set uid(input) {
    if (!input) return;
    const uid = parseInt(input);
    // don't fight the river, go with the flow
    let getEmail = new Promise( (resolve, reject) => {
      yourDataBase.getByUID(uid, (err, result) => {
        if (err) return reject(err);
        resolve(result);
      });
    });
    // kick off the promise, which will be async all on its own
    getEmail()
    .then(result => {
      this.renderLoaded(result.message);
    })
    .catch(error => {
      this.renderError(error);
    });
  }
};

customElements.define('e-mail', EmailElement);

Và sau đó, bạn thực hiện các hàm renderLoaded / renderError xử lý các lệnh gọi sự kiện và bóng dom:

  renderLoaded(message) {
    const shadowRoot = this.attachShadow({mode: 'open'});
    shadowRoot.innerHTML = `
      <div class="email">A random email message has appeared. ${message}</div>
    `;
    // is there an ancient event listener?
    if (this.onload) {
      this.onload(...);
    }
    // there might be modern event listeners. dispatch an event.
    this.dispatchEvent(new Event('load', ...));
  }

  renderFailed() {
    const shadowRoot = this.attachShadow({mode: 'open'});
    shadowRoot.innerHTML = `
      <div class="email">No email messages.</div>
    `;
    // is there an ancient event listener?
    if (this.onload) {
      this.onerror(...);
    }
    // there might be modern event listeners. dispatch an event.
    this.dispatchEvent(new Event('error', ...));
  }

Cũng lưu ý rằng tôi đã thay đổi idthành a class, bởi vì trừ khi bạn viết một số mã lạ để chỉ cho phép một phiên bản <e-mail>phần tử của bạn trên một trang, bạn không thể sử dụng một mã định danh duy nhất và sau đó gán nó cho một loạt các phần tử.


2

Tôi đã thực hiện trường hợp thử nghiệm này dựa trên câu trả lời của @ Downgoat.
Nó chạy trên NodeJS. Đây là mã của Downgoat trong đó phần async được cung cấp bởi một setTimeout()cuộc gọi.

'use strict';
const util = require( 'util' );

class AsyncConstructor{

  constructor( lapse ){
    this.qqq = 'QQQ';
    this.lapse = lapse;
    return ( async ( lapse ) => {
      await this.delay( lapse );
      return this;
    })( lapse );
  }

  async delay(ms) {
    return await new Promise(resolve => setTimeout(resolve, ms));
  }

}

let run = async ( millis ) => {
  // Instatiate with await, inside an async function
  let asyncConstructed = await new AsyncConstructor( millis );
  console.log( 'AsyncConstructor: ' + util.inspect( asyncConstructed ));
};

run( 777 );

Trường hợp sử dụng của tôi là DAO cho phía máy chủ của ứng dụng web.
Như tôi thấy DAO, mỗi cái đều được liên kết với một định dạng bản ghi, trong trường hợp của tôi, một bộ sưu tập MongoDB như một đầu bếp.
Ví dụ cooksDAO chứa dữ liệu của cook.
Trong tâm trí bồn chồn, tôi sẽ có thể khởi tạo DAO của đầu bếp cung cấp cookId làm đối số, và việc tạo tức thời sẽ tạo ra đối tượng và điền dữ liệu vào đầu bếp.
Do đó, cần phải chạy công cụ async vào hàm tạo.
Tôi muốn viết:

let cook = new cooksDAO( '12345' );  

để có các thuộc tính có sẵn như cook.getDisplayName().
Với giải pháp này tôi phải làm:

let cook = await new cooksDAO( '12345' );  

đó là rất giống với lý tưởng.
Ngoài ra, tôi cần phải làm điều này trong một asyncchức năng.

Kế hoạch B của tôi là để dữ liệu tải ra khỏi hàm tạo, dựa trên đề xuất @slebetman để sử dụng hàm init và làm một cái gì đó như thế này:

let cook = new cooksDAO( '12345' );  
async cook.getData();

mà không phá vỡ các quy tắc.


2

sử dụng phương thức async trong cấu trúc ???

constructor(props) {
    super(props);
    (async () => await this.qwe(() => console.log(props), () => console.log(props)))();
}

async qwe(q, w) {
    return new Promise((rs, rj) => {
        rs(q());
        rj(w());
    });
}

2

Giải pháp ngăn chặn

Bạn có thể tạo một async init() {... return this;}phương thức, sau đó thay vào đó làm new MyClass().init()bất cứ khi nào bạn thường nói new MyClass().

Điều này không rõ ràng vì nó phụ thuộc vào tất cả những người sử dụng mã của bạn và chính bạn, để luôn khởi tạo đối tượng như vậy. Tuy nhiên nếu bạn chỉ sử dụng đối tượng này ở một hoặc hai vị trí cụ thể trong mã của mình, điều đó có thể ổn.

Một vấn đề quan trọng xảy ra vì ES không có hệ thống loại, vì vậy nếu bạn quên gọi nó, bạn vừa trả về undefinedvì hàm tạo không trả về gì. Giáo sư. Tốt hơn nhiều sẽ là làm một cái gì đó như:

Điều tốt nhất để làm là:

class AsyncOnlyObject {
    constructor() {
    }
    async init() {
        this.someField = await this.calculateStuff();
    }

    async calculateStuff() {
        return 5;
    }
}

async function newAsync_AsyncOnlyObject() {
    return await new AsyncOnlyObject().init();
}

newAsync_AsyncOnlyObject().then(console.log);
// output: AsyncOnlyObject {someField: 5}

Giải pháp phương pháp nhà máy (tốt hơn một chút)

Tuy nhiên, sau đó bạn có thể vô tình làm AsyncOnlyObject mới, có lẽ bạn chỉ nên tạo chức năng nhà máy sử dụng Object.create(AsyncOnlyObject.prototype)trực tiếp:

async function newAsync_AsyncOnlyObject() {
    return await Object.create(AsyncOnlyObject.prototype).init();
}

newAsync_AsyncOnlyObject().then(console.log);
// output: AsyncOnlyObject {someField: 5}

Tuy nhiên, nói rằng bạn muốn sử dụng mô hình này trên nhiều đối tượng ... bạn có thể trừu tượng hóa nó như một công cụ trang trí hoặc một cái gì đó mà bạn (bằng lời nói, ugh) gọi sau khi định nghĩa như thế postProcess_makeAsyncInit(AsyncOnlyObject), nhưng ở đây tôi sẽ sử dụng extendsvì nó phù hợp với ngữ nghĩa của lớp con (các lớp con là lớp cha + thêm, trong đó chúng phải tuân theo hợp đồng thiết kế của lớp cha và có thể làm thêm điều gì đó, một lớp con không đồng bộ sẽ là lạ nếu cha mẹ cũng không đồng bộ, vì nó không thể được khởi tạo giống nhau đường):


Giải pháp trừu tượng (phiên bản mở rộng / lớp con)

class AsyncObject {
    constructor() {
        throw new Error('classes descended from AsyncObject must be initialized as (await) TheClassName.anew(), rather than new TheClassName()');
    }

    static async anew(...args) {
        var R = Object.create(this.prototype);
        R.init(...args);
        return R;
    }
}

class MyObject extends AsyncObject {
    async init(x, y=5) {
        this.x = x;
        this.y = y;
        // bonus: we need not return 'this'
    }
}

MyObject.anew('x').then(console.log);
// output: MyObject {x: "x", y: 5}

(không sử dụng trong sản xuất: Tôi chưa nghĩ đến các tình huống phức tạp như liệu đây có phải là cách thích hợp để viết trình bao cho các đối số từ khóa hay không.)


2

Không giống như những người khác đã nói, bạn có thể làm cho nó hoạt động.

JavaScript classes có thể trả về bất cứ thứ gì theo nghĩa đen từ chúng constructor, thậm chí là một thể hiện của lớp khác. Vì vậy, bạn có thể trả về một Promisetừ hàm tạo của lớp mà phân giải thành thể hiện thực của nó.

Dưới đây là một ví dụ:

export class Foo {

    constructor() {

        return (async () => {

            // await anything you want

            return this; // Return the newly-created instance
        }).call(this);
    }
}

Sau đó, bạn sẽ tạo các trường hợp Footheo cách này:

const foo = await new Foo();

1

Nếu bạn có thể tránh extend , bạn có thể tránh các lớp học tất cả với nhau và sử dụng hàm hợp như nhà xây dựng . Bạn có thể sử dụng các biến trong phạm vi thay vì các thành viên lớp:

async function buildA(...) {
  const data = await fetch(...);
  return {
    getData: function() {
      return data;
    }
  }
}

và sử dụng đơn giản như

const a = await buildA(...);

Nếu bạn đang sử dụng bản in hoặc luồng, bạn thậm chí có thể thực thi giao diện của các hàm tạo

Interface A {
  getData: object;
}

async function buildA0(...): Promise<A> { ... }
async function buildA1(...): Promise<A> { ... }
...

0

Biến thể trên mẫu trình xây dựng, sử dụng lệnh gọi ():

function asyncMethod(arg) {
    function innerPromise() { return new Promise((...)=> {...}) }
    innerPromise().then(result => {
        this.setStuff(result);
    }
}

const getInstance = async (arg) => {
    let instance = new Instance();
    await asyncMethod.call(instance, arg);
    return instance;
}

0

Bạn có thể ngay lập tức gọi một hàm async ẩn danh trả về tin nhắn và đặt nó thành biến tin nhắn. Bạn có thể muốn xem các biểu thức hàm được gọi ngay lập tức (IEFES), trong trường hợp bạn không quen với mẫu này. Điều này sẽ làm việc như một nét duyên dáng.

var message = (async function() { return await grabUID(uid) })()

-1

Câu trả lời được chấp nhận của @ slebetmen giải thích rõ tại sao điều này không hiệu quả. Ngoài hai mẫu được trình bày trong câu trả lời đó, một tùy chọn khác là chỉ truy cập các thuộc tính async của bạn thông qua một getter async tùy chỉnh. Sau đó, hàm tạo () có thể kích hoạt việc tạo không đồng bộ các thuộc tính, nhưng getter sau đó kiểm tra xem thuộc tính có sẵn trước khi sử dụng hoặc trả về nó không.

Cách tiếp cận này đặc biệt hữu ích khi bạn muốn khởi tạo một đối tượng toàn cầu một lần khi khởi động và bạn muốn thực hiện nó trong một mô-đun. Thay vì khởi tạo trong bạn index.jsvà chuyển thể hiện ở những nơi cần nó, chỉ cần requiremô-đun của bạn ở bất cứ nơi nào cần đối tượng toàn cầu.

Sử dụng

const instance = new MyClass();
const prop = await instance.getMyProperty();

Thực hiện

class MyClass {
  constructor() {
    this.myProperty = null;
    this.myPropertyPromise = this.downloadAsyncStuff();
  }
  async downloadAsyncStuff() {
    // await yourAsyncCall();
    this.myProperty = 'async property'; // this would instead by your async call
    return this.myProperty;
  }
  getMyProperty() {
    if (this.myProperty) {
      return this.myProperty;
    } else {
      return this.myPropertyPromise;
    }
  }
}

-2

Các câu trả lời khác là thiếu rõ ràng. Chỉ cần gọi một hàm async từ hàm tạo của bạn:

constructor() {
    setContentAsync();
}

async setContentAsync() {
    let uid = this.getAttribute('data-uid')
    let message = await grabUID(uid)

    const shadowRoot = this.attachShadow({mode: 'open'})
    shadowRoot.innerHTML = `
      <div id="email">A random email message has appeared. ${message}</div>
    `
}

Giống như một câu trả lời "rõ ràng" khác ở đây , câu trả lời này sẽ không làm những gì mà lập trình viên thường mong đợi ở một nhà xây dựng, tức là nội dung được đặt khi đối tượng được tạo.
Dan Dascalescu

2
@DanDascalescu Nó được thiết lập, không đồng bộ, đó chính xác là những gì người hỏi yêu cầu. Quan điểm của bạn là nội dung không được đặt đồng bộ khi đối tượng được tạo, câu hỏi không bắt buộc. Đó là lý do tại sao câu hỏi là về việc sử dụng await / async từ bên trong một hàm tạo. Tôi đã trình bày cách bạn có thể gọi bao nhiêu await / async như bạn muốn từ một nhà xây dựng bằng cách gọi một hàm async từ nó. Tôi đã trả lời câu hỏi một cách hoàn hảo.
Navigateur

@Navig Nghiệp đó là giải pháp tương tự tôi đã đưa ra, nhưng các ý kiến ​​về một câu hỏi tương tự khác cho thấy không nên thực hiện theo cách này. Vấn đề chính là một lời hứa bị mất trong hàm tạo, và đây là phản mẫu. Bạn có bất kỳ tài liệu tham khảo nào mà nó đề xuất cách tiếp cận này để gọi một hàm async từ hàm tạo của bạn không?
Marklar

1
@Marklar không có tài liệu tham khảo, tại sao bạn cần bất kỳ? Sẽ không có vấn đề gì nếu thứ gì đó bị "mất" nếu bạn không cần nó ngay từ đầu. Và nếu bạn thực sự cần lời hứa, việc thêm this.myPromise =vào (theo nghĩa chung) là không quan trọng để không phải là một mô hình chống đối theo bất kỳ ý nghĩa nào. Có những trường hợp hoàn toàn hợp lệ cho cần phải đá ra một thuật toán async, sau khi xây dựng, mà không có giá trị trả về chính nó, và thêm một hệ nào đơn giản, vì vậy bất cứ ai đang tư vấn cho không để làm điều này là hiểu lầm gì đó
Navigateur

1
Cảm ơn đã dành thời gian để trả lời. Tôi đang tìm đọc thêm vì những câu trả lời mâu thuẫn ở đây trên Stackoverflow. Tôi đã hy vọng xác nhận một số thực tiễn tốt nhất cho kịch bản này.
Marklar

-2

Bạn nên thêm thenchức năng để ví dụ. Promisesẽ tự động nhận ra nó là một đối tượng có thể Promise.resolvetự động

const asyncSymbol = Symbol();
class MyClass {
    constructor() {
        this.asyncData = null
    }
    then(resolve, reject) {
        return (this[asyncSymbol] = this[asyncSymbol] || new Promise((innerResolve, innerReject) => {
            this.asyncData = { a: 1 }
            setTimeout(() => innerResolve(this.asyncData), 3000)
        })).then(resolve, reject)
    }
}

async function wait() {
    const asyncData = await new MyClass();
    alert('run 3s later')
    alert(asyncData.a)
}

innerResolve(this)sẽ không hoạt động, như thisvẫn còn có thể. Điều này dẫn đến một giải pháp đệ quy không bao giờ kết thúc.
Bergi
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.