ตัวสร้างคลาส Async / Await


169

ในขณะนี้ฉันกำลังพยายามใช้async/awaitภายในฟังก์ชันตัวสร้างคลาส นี่คือเพื่อให้ฉันสามารถรับe-mailแท็กที่กำหนดเองสำหรับโครงการอิเล็กตรอนที่ฉันกำลังทำงานอยู่

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

อย่างไรก็ตามในขณะนี้โครงการไม่ทำงานโดยมีข้อผิดพลาดดังต่อไปนี้:

Class constructor may not be an async method

มีวิธีที่จะหลีกเลี่ยงสิ่งนี้เพื่อให้ฉันสามารถใช้ async / รออยู่ในนี้ แทนที่จะต้องโทรกลับหรือ. แล้ว ()


6
วัตถุประสงค์ของตัวสร้างคือการจัดสรรวัตถุให้คุณแล้วส่งคืนทันที คุณจะเจาะจงมากขึ้นได้ไหมว่าทำไมคุณถึงคิดว่าคอนสตรัคเตอร์ของคุณควรเป็นแบบอะซิงก์? เพราะเราเกือบรับประกันว่าจะจัดการกับปัญหา XYที่นี่
Kamermans 'Pomax' ของ Mike

4
@ Mike'Pomax'Kamermans นั่นเป็นไปได้ทีเดียว โดยทั่วไปฉันจำเป็นต้องค้นหาฐานข้อมูลเพื่อให้ได้ข้อมูลเมตาที่จำเป็นในการโหลดองค์ประกอบนี้ การสืบค้นฐานข้อมูลเป็นการดำเนินการแบบอะซิงโครนัสและด้วยเหตุนี้ฉันต้องการวิธีที่จะรอให้เสร็จก่อนที่จะสร้างองค์ประกอบ ฉันไม่ต้องการใช้การโทรกลับเนื่องจากฉันใช้คอย / อะซิงก์ตลอดโครงการที่เหลือและต้องการรักษาความต่อเนื่อง
Alexander Craggs

@ Mike'Pomax'Kamermans บริบทที่สมบูรณ์ของสิ่งนี้คือไคลเอนต์อีเมลซึ่งองค์ประกอบ HTML แต่ละรายการมีลักษณะคล้ายกับที่<e-mail data-uid="1028"></email>และมีการเติมข้อมูลด้วยการใช้customElements.define()วิธีการ
Alexander Craggs

คุณแทบไม่ต้องการให้นวกรรมิกเป็น async สร้างตัวสร้างแบบซิงโครนัสที่ส่งคืนวัตถุของคุณจากนั้นใช้วิธี.init()การทำสิ่งต่าง ๆแบบอะซิงโครนัส นอกจากนี้เนื่องจากคุณเป็น sublcass HTMLElement เป็นไปได้อย่างมากว่ารหัสที่ใช้คลาสนี้ไม่มีความคิดว่ามันเป็น async ดังนั้นคุณน่าจะต้องหาทางออกที่แตกต่างกันอยู่ดี
jfriend00

คำตอบ:


264

สิ่งนี้ไม่สามารถทำงานได้

asyncคำหลักที่ช่วยให้awaitเพื่อนำไปใช้ในการทำงานทำเครื่องหมายว่าเป็นasyncแต่มันยังแปลงฟังก์ชั่นที่เป็นเครื่องกำเนิดไฟฟ้าสัญญา ดังนั้นฟังก์ชั่นที่ถูกทำเครื่องหมายด้วยasyncจะคืนสัญญา คอนสตรัคในมืออื่น ๆ ส่งกลับวัตถุมันกำลังสร้าง ดังนั้นเราจึงมีสถานการณ์ที่คุณต้องการคืนทั้งวัตถุและสัญญา: สถานการณ์ที่เป็นไปไม่ได้

คุณสามารถใช้ async / รอที่คุณสามารถใช้สัญญาได้เพราะพวกเขาเป็นไวยากรณ์น้ำตาลสำหรับสัญญา คุณไม่สามารถใช้สัญญาในตัวสร้างได้เนื่องจากตัวสร้างต้องส่งคืนวัตถุที่จะสร้างไม่ใช่สัญญา

มีสองรูปแบบการออกแบบเพื่อเอาชนะสิ่งนี้ทั้งสองคิดค้นก่อนสัญญาอยู่

  1. การใช้init()ฟังก์ชั่น นี้ทำงานบิตเช่น .ready()jQuery วัตถุที่คุณสร้างสามารถใช้ได้ภายในของตัวเองinitหรือreadyฟังก์ชั่น:

    การใช้งาน:

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

    การดำเนินงาน:

    class myClass {
        constructor () {
    
        }
    
        init (callback) {
            // do something async and call the callback:
            callback.bind(this)();
        }
    }
  2. ใช้ตัวสร้าง ฉันไม่เห็นสิ่งนี้ใช้ในจาวาสคริปต์มากนัก แต่นี่เป็นหนึ่งในวิธีแก้ปัญหาทั่วไปใน Java เมื่อวัตถุต้องถูกสร้างแบบอะซิงโครนัส แน่นอนว่ารูปแบบการสร้างถูกใช้เมื่อสร้างวัตถุที่ต้องการพารามิเตอร์ที่ซับซ้อนจำนวนมาก ซึ่งเป็นกรณีใช้ตรงสำหรับตัวสร้างแบบอะซิงโครนัส ข้อแตกต่างคือตัวสร้าง async ไม่ส่งคืนวัตถุ แต่เป็นสัญญาของวัตถุนั้น:

    การใช้งาน:

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

    การดำเนินงาน:

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

    การใช้งานกับ async / คอย:

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

หมายเหตุ: แม้ว่าในตัวอย่างข้างต้นเราใช้สัญญาสำหรับผู้สร้าง async พวกเขาไม่จำเป็นต้องพูดอย่างเคร่งครัด คุณสามารถเขียนเครื่องมือสร้างที่ยอมรับการติดต่อกลับได้ง่ายๆ


หมายเหตุเกี่ยวกับฟังก์ชั่นการโทรภายในฟังก์ชั่นคงที่

สิ่งนี้ไม่มีส่วนเกี่ยวข้องใด ๆ กับตัวสร้าง async แต่มีความthisหมายอย่างไรกับคำหลักจริง ๆ (ซึ่งอาจเป็นเรื่องที่น่าแปลกใจสำหรับผู้ที่มาจากภาษาที่ทำการแก้ไขชื่อเมธอดโดยอัตโนมัตินั่นคือภาษาที่ไม่ต้องการthisคำหลัก)

thisคำหลักหมายถึงวัตถุ instantiated ไม่ใช่ชั้นเรียน ดังนั้นโดยปกติคุณไม่สามารถใช้thisภายในฟังก์ชั่นแบบคงที่ได้เนื่องจากฟังก์ชั่นแบบคงที่ไม่ได้ถูกผูกไว้กับวัตถุใด ๆ แต่ถูกผูกเข้ากับคลาสโดยตรง

กล่าวคือในรหัสต่อไปนี้:

class A {
    static foo () {}
}

คุณไม่สามารถทำ:

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

คุณต้องเรียกมันว่า:

A.foo();

ดังนั้นรหัสต่อไปนี้จะส่งผลให้เกิดข้อผิดพลาด:

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

หากต้องการแก้ไขคุณสามารถสร้างbarฟังก์ชั่นปกติหรือวิธีการคงที่:

function bar1 () {}

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

    static bar2 () {}
}

โปรดทราบว่าตามความคิดเห็นความคิดก็คือนี่เป็นองค์ประกอบ html ซึ่งโดยทั่วไปจะไม่มีคู่มือinit()แต่มีฟังก์ชันที่เชื่อมโยงกับคุณลักษณะเฉพาะเช่นsrcหรือhref(และในกรณีนี้data-uid) ซึ่งหมายถึงการใช้ setter ที่ทั้งสอง ผูกและเริ่มต้นใหม่ทุกครั้งที่มีการผูกมัดค่าใหม่ (และอาจเป็นไปได้ในระหว่างการก่อสร้างเช่นกัน แต่แน่นอนโดยไม่ต้องรอเส้นทางของรหัสผลลัพธ์)
Mike 'Pomax' Kamermans

คุณควรแสดงความคิดเห็นว่าทำไมคำตอบด้านล่างไม่เพียงพอ (ถ้าเป็น) หรือที่อยู่เป็นอย่างอื่น
Augie Gardner

ฉันอยากรู้ว่าทำไมbindจะต้องในตัวอย่างแรกcallback.bind(this)();? เพื่อให้คุณสามารถทำสิ่งต่าง ๆ เช่นthis.otherFunc()ภายในโทรกลับ?
Alexander Craggs

1
@AlexanderCraggs มันเป็นเพียงความสะดวกสบายเพื่อให้ในการเรียกกลับหมายถึงthis myClassหากคุณมักจะใช้myObjแทนคุณthisไม่ต้องการมัน
slebetman

1
ขณะนี้มีข้อ จำกัด เกี่ยวกับภาษา แต่ฉันไม่เห็นว่าทำไมในอนาคตคุณไม่สามารถมีconst a = await new A()วิธีเดียวกับที่เรามีฟังก์ชั่นปกติและฟังก์ชั่น async
7ynk3r

138

คุณสามารถทำสิ่งนี้ได้อย่างแน่นอน โดยทั่วไป:

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

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

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

เพื่อสร้างการใช้คลาส:

let instance = await new AsyncConstructor();

วิธีนี้มีความล้มเหลวสั้น ๆ เล็กน้อย:

superหมายเหตุ : หากคุณต้องการใช้superคุณไม่สามารถโทรภายใน async callback

typescript หมายเหตุ:นี้ปัญหาสาเหตุที่มี typescript เพราะผลตอบแทนที่สร้างพิมพ์แทนPromise<MyClass> MyClassไม่มีวิธีที่ชัดเจนในการแก้ไขปัญหานี้ที่ฉันรู้ วิธีหนึ่งที่เป็นไปได้ที่แนะนำโดย @blitter คือการใส่/** @type {any} */จุดเริ่มต้นของตัวสร้าง - ฉันไม่ทราบว่าสิ่งนี้ใช้ได้ในทุกสถานการณ์หรือไม่


1
@ PastheLoD ฉันไม่คิดว่ามันจะแก้ปัญหาโดยที่ไม่ต้องส่งคืน แต่คุณกำลังบอกว่าจะทำเช่นนั้นฉันจะตรวจสอบข้อมูลจำเพาะและอัปเดต ...
Downgoat

2
@JuanLanus บล็อก async จะจับพารามิเตอร์โดยอัตโนมัติดังนั้นสำหรับอาร์กิวเมนต์ x คุณต้องทำเท่านั้นconstructor(x) { return (async()=>{await f(x); return this})() }
Downgoat

1
@PAStheLoD: return thisเป็นสิ่งที่จำเป็นเพราะในขณะที่constructorมันทำโดยอัตโนมัติสำหรับคุณนั้น async IIFE ไม่ได้และคุณจะกลับมาคืนค่าวัตถุว่างเปล่า
Dan Dascalescu

1
ขณะนี้เป็น TS 3.5.1 ที่กำหนดเป้าหมาย ES5, ES2017, ES2018 (และอาจเป็นอย่างอื่น แต่ฉันไม่ได้ตรวจสอบ) หากคุณส่งคืนคอนสตรัคเตอร์คุณจะได้รับข้อความแสดงข้อผิดพลาดนี้: "ประเภทการส่งคืนของคอนสตรัคเตอร์ ประเภทอินสแตนซ์ของคลาส " ประเภทของ IIFE คือสัญญา <this> และเนื่องจากชั้นเรียนไม่ได้เป็นสัญญา <T> ฉันจึงไม่เห็นว่ามันจะทำงานได้อย่างไร (คุณสามารถส่งคืนสิ่งอื่นนอกเหนือจาก 'สิ่งนี้' ได้อย่างไร) นี่หมายความว่าการส่งคืนทั้งสองนั้นไม่จำเป็น (ตัวนอกนั้นแย่กว่าเล็กน้อยเนื่องจากมันนำไปสู่ข้อผิดพลาดในการคอมไพล์)
PAStheLoD

3
@PAStheLoD ใช่นั่นเป็นข้อ จำกัด แบบอักษร โดยทั่วไปใน JS คลาสTควรกลับมาTเมื่อสร้าง แต่จะได้รับความสามารถ async ที่เรากลับมาPromise<T>ซึ่งแก้ไขไปthisแต่ที่สับสน typescript คุณต้องการผลตอบแทนภายนอกไม่เช่นนั้นคุณจะไม่รู้เมื่อสัญญาเสร็จสิ้น - ดังนั้นวิธีการนี้จะไม่ทำงานกับ TypeScript (เว้นแต่จะมีการแฮ็กที่มีนามแฝงประเภท?) ไม่ใช่ผู้เชี่ยวชาญด้าน
วรรณกรรม

7

เนื่องจากฟังก์ชั่น async เป็นสัญญาคุณสามารถสร้างฟังก์ชั่นคงที่ในชั้นเรียนของคุณซึ่งดำเนินการฟังก์ชั่น async ซึ่งส่งกลับตัวอย่างของชั้นเรียน:

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()

โทรด้วย let yql = await Yql.init()จากฟังก์ชั่น async


5

จากความคิดเห็นของคุณคุณน่าจะทำทุกอย่างที่ HTMLElement พร้อมกับการโหลดสินทรัพย์ทำ: ให้ constructor เริ่มต้นการดำเนินการด้านข้างสร้างการโหลดหรือเหตุการณ์ข้อผิดพลาดขึ้นอยู่กับผลลัพธ์

ใช่นั่นหมายถึงการใช้สัญญา แต่มันก็หมายถึง "ทำสิ่งต่าง ๆ เช่นเดียวกับองค์ประกอบ HTML อื่น ๆ " ดังนั้นคุณจึงอยู่ใน บริษัท ที่ดี ตัวอย่างเช่น

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

เตะออกนี้โหลดไม่ตรงกันของสินทรัพย์แหล่งที่มาว่าเมื่อมันประสบความสำเร็จในปลายและเมื่อมันผิดพลาดในปลายonload onerrorดังนั้นให้ชั้นเรียนของคุณทำสิ่งนี้ด้วย:

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

จากนั้นคุณทำให้ฟังก์ชั่น renderLoaded / renderError จัดการกับการเรียกเหตุการณ์และเงาโดม:

  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', ...));
  }

นอกจากนี้โปรดทราบว่าฉันเปลี่ยนของคุณidเป็น a classเพราะหากคุณไม่ได้เขียนโค้ดแปลก ๆ เพื่อให้อนุญาตอินสแตนซ์เดียวของ<e-mail>องค์ประกอบของคุณบนหน้าเว็บคุณไม่สามารถใช้ตัวระบุที่ไม่ซ้ำกันแล้วกำหนดให้กับองค์ประกอบมากมาย


2

ฉันสร้างกรณีทดสอบตามคำตอบของ @ Downgoat
มันทำงานบน NodeJS นี่คือรหัสของ Downgoat ที่ให้ส่วน async จากการsetTimeout()โทร

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

กรณีการใช้งานของฉันคือ DAO สำหรับฝั่งเซิร์ฟเวอร์ของเว็บแอปพลิเคชัน
ตามที่ฉันเห็น DAO พวกเขาแต่ละคนเชื่อมโยงกับรูปแบบการบันทึกในกรณีของฉันคอลเลกชัน MongoDB เช่นตัวปรุงอาหาร
อินสแตนซ์ cooksDAO เก็บข้อมูลของคุ้กกี้
ในใจที่กระสับกระส่ายของฉันฉันจะสามารถยกตัวอย่าง DAO ของพ่อครัวที่ให้แม่ครัวเป็นอาร์กิวเมนต์และการสร้างอินสแตนซ์จะสร้างวัตถุและเติมด้วยข้อมูลของแม่ครัว
ดังนั้นจึงจำเป็นต้องเรียกใช้ async สิ่งที่เป็นตัวสร้าง
ฉันต้องการเขียน:

let cook = new cooksDAO( '12345' );  

cook.getDisplayName()ที่จะมีคุณสมบัติที่ใช้ได้เช่น
ด้วยวิธีนี้ฉันต้องทำ:

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

ซึ่งคล้ายกับอุดมคติ
นอกจากนี้ฉันต้องทำสิ่งนี้ภายในasyncฟังก์ชั่น

B-plan ของฉันคือการปล่อยให้ข้อมูลโหลดออกจากนวกรรมิกตามคำแนะนำ @slebetman เพื่อใช้ฟังก์ชั่น init และทำสิ่งนี้:

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

ซึ่งไม่ผิดกฎ


2

ใช้วิธีการ async ในการสร้าง ???

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

วิธีแก้ปัญหา stopgap

คุณสามารถสร้างasync init() {... return this;}วิธีการได้จากนั้นทำnew MyClass().init()ทุกครั้งที่คุณพูดตามปกติnew MyClass()เมื่อใดก็ตามที่คุณต้องการได้ตามปกติเพียงแค่พูดว่า

สิ่งนี้ไม่สะอาดเพราะอาศัยทุกคนที่ใช้รหัสของคุณและตัวคุณเองเพื่อยกตัวอย่างวัตถุเช่นนั้น อย่างไรก็ตามหากคุณใช้วัตถุนี้ในสถานที่หนึ่งหรือสองแห่งในรหัสของคุณเท่านั้นมันอาจจะใช้ได้

แม้ว่าปัญหาสำคัญเกิดขึ้นเนื่องจาก ES ไม่มีระบบชนิดดังนั้นถ้าคุณลืมที่จะเรียกมันว่าคุณเพิ่งกลับมา undefinedเพราะคอนสตรัคไม่ส่งคืนอะไรเลย อุ่ย ดีกว่ามากที่จะทำสิ่งที่ชอบ:

สิ่งที่ดีที่สุดที่ควรทำคือ:

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}

วิธีการแก้ปัญหาโรงงาน (ดีกว่าเล็กน้อย)

อย่างไรก็ตามคุณอาจทำ AsyncOnlyObject ใหม่โดยไม่ได้ตั้งใจคุณควรสร้างฟังก์ชันโรงงานที่ใช้Object.create(AsyncOnlyObject.prototype)โดยตรง:

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

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

อย่างไรก็ตามบอกว่าคุณต้องการใช้รูปแบบนี้กับวัตถุหลายอย่าง ... คุณสามารถสรุปสิ่งนี้เป็นมัณฑนากรหรือสิ่งที่คุณโทร (verbosely, ฮึ) หลังจากที่กำหนดเช่นpostProcess_makeAsyncInit(AsyncOnlyObject)นี้ แต่ที่นี่ฉันจะใช้extendsเพราะมันเหมาะกับความหมายคลาสย่อย (คลาสย่อยเป็นคลาสพาเรนต์ + พิเศษซึ่งพวกเขาควรทำตามสัญญาการออกแบบของคลาสพาเรนต์และอาจทำสิ่งเพิ่มเติมคลาสย่อย async จะแปลกถ้าผู้ปกครองไม่ได้เป็นแบบอะซิงก์เพราะไม่สามารถเริ่มต้นเดียวกันได้ เดียว):


โซลูชันที่สรุปไว้ (เวอร์ชันขยาย / รอง)

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}

(ไม่ได้ใช้ในการผลิต: ฉันไม่ได้คิดผ่านสถานการณ์ที่ซับซ้อนเช่นนี้เป็นวิธีที่เหมาะสมในการเขียนเสื้อคลุมสำหรับอาร์กิวเมนต์ของคำหลัก)


2

ไม่เหมือนคนอื่น ๆ ที่พูดว่าคุณสามารถทำให้มันทำงานได้

จาวาสคริปต์classes สามารถส่งคืนค่าอะไรก็ได้จากตัวของconstructorมันแม้แต่ตัวอย่างของคลาสอื่น ดังนั้นคุณอาจกลับมาPromiseจากนวกรรมิกของคลาสของคุณที่แก้ไขไปยังอินสแตนซ์ที่แท้จริงของมัน

ด้านล่างเป็นตัวอย่าง:

export class Foo {

    constructor() {

        return (async () => {

            // await anything you want

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

จากนั้นคุณจะสร้างอินสแตนซ์ของFooวิธีนี้:

const foo = await new Foo();

1

หากคุณสามารถหลีกเลี่ยง extendคุณสามารถหลีกเลี่ยงการเรียนทั้งหมดเข้าด้วยกันและใช้องค์ประกอบเป็นฟังก์ชั่นการก่อสร้าง คุณสามารถใช้ตัวแปรในขอบเขตแทนสมาชิกคลาส:

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

และใช้ง่ายเป็น

const a = await buildA(...);

หากคุณใช้ typescript หรือ flow คุณสามารถบังคับใช้อินเตอร์เฟสของ Constructor ได้

Interface A {
  getData: object;
}

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

0

ความหลากหลายของรูปแบบตัวสร้างโดยใช้การโทร ():

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

คุณสามารถเรียกใช้ฟังก์ชัน async แบบไม่ระบุชื่อได้ทันทีซึ่งส่งคืนข้อความและตั้งเป็นตัวแปรข้อความ คุณอาจต้องการดูฟังก์ชั่นที่เรียกใช้งานได้ทันที (IEFES) ในกรณีที่คุณไม่คุ้นเคยกับรูปแบบนี้ นี้จะทำงานเหมือนมีเสน่ห์

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

-1

@ slebetmen คำตอบที่ยอมรับได้อธิบายได้ดีว่าทำไมสิ่งนี้ถึงไม่ได้ นอกเหนือจากสองรูปแบบที่แสดงในคำตอบนั้นอีกตัวเลือกหนึ่งคือเข้าถึงคุณสมบัติ async ของคุณผ่านทาง async getter ที่กำหนดเองเท่านั้น Constructor () สามารถทริกเกอร์การสร้าง async ของคุณสมบัติได้ แต่ Getter จะตรวจสอบเพื่อดูว่าคุณสมบัตินั้นมีอยู่ก่อนที่มันจะใช้หรือส่งคืนหรือไม่

วิธีการนี้มีประโยชน์อย่างยิ่งเมื่อคุณต้องการเริ่มต้นวัตถุทั่วโลกเมื่อเริ่มต้นและคุณต้องการที่จะทำมันภายในโมดูล แทนที่จะเริ่มต้นในของคุณindex.jsและส่งต่ออินสแตนซ์ในสถานที่ที่ต้องการเพียงrequireโมดูลของคุณทุกที่ที่ต้องการวัตถุระดับโลก

การใช้

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

การดำเนินงาน

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

คำตอบอื่น ๆ จะหายไปอย่างชัดเจน เพียงเรียกใช้ฟังก์ชัน async จากนวกรรมิกของคุณ:

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

เช่นเดียวกับคำตอบ "ชัดเจน" อื่นที่นี่คนนี้จะไม่ทำสิ่งที่โปรแกรมเมอร์คาดหวังโดยทั่วไปของคอนสตรัคเตอร์คือเนื้อหาถูกตั้งค่าเมื่อมีการสร้างวัตถุ
Dan Dascalescu

2
@DanDascalescu มันตั้งเพียงแบบอะซิงโครนัสซึ่งเป็นสิ่งที่ผู้ถามต้องการ ประเด็นของคุณคือเนื้อหาไม่ได้ถูกตั้งค่าพร้อมกันเมื่อมีการสร้างวัตถุซึ่งไม่จำเป็นต้องมีคำถาม นั่นเป็นเหตุผลที่คำถามเกี่ยวกับการใช้คอย / async จากภายในตัวสร้าง ฉันได้สาธิตวิธีที่คุณสามารถเรียกใช้ / async ได้มากเท่าที่คุณต้องการจากนวกรรมิกโดยการเรียกใช้ฟังก์ชัน async จากมัน ฉันตอบคำถามอย่างสมบูรณ์
Navigateur

@Navigateur ที่เป็นคำตอบเดียวกับที่ฉันคิดไว้ แต่ความเห็นเกี่ยวกับคำถามที่คล้ายกันแนะนำว่าไม่ควรทำแบบนี้ ปัญหาหลักของการเป็นสัญญาจะหายไปในตัวสร้างและนี่คือปฏิสสาร คุณมีการอ้างอิงที่แนะนำวิธีการเรียกใช้ฟังก์ชัน async จากนวกรรมิกของคุณหรือไม่?
Marklar

1
@ Marklar ไม่มีการอ้างอิงทำไมคุณต้องการอะไรบ้าง มันไม่สำคัญว่าอะไรจะ "หายไป" ถ้าคุณไม่ต้องการมันในตอนแรก และถ้าคุณต้องการคำมั่นสัญญามันเป็นการเพิ่มเล็กน้อยthis.myPromise =(ในความหมายทั่วไป) ดังนั้นจึงไม่ใช่รูปแบบการต่อต้านใด ๆ ทั้งสิ้น มีกรณีที่ถูกต้องอย่างสมบูรณ์สำหรับการเริ่มต้นใช้งานอัลกอริทึมแบบอะซิงโครนัสเมื่อสร้างขึ้นซึ่งไม่มีค่าตอบแทนและเพิ่มเราอย่างง่ายต่อไปดังนั้นใครก็ตามที่แนะนำว่าอย่าทำเช่นนี้จะเข้าใจผิด
Navigateur

1
ขอบคุณที่สละเวลาตอบ ฉันกำลังมองหาการอ่านเพิ่มเติมเนื่องจากคำตอบที่ขัดแย้งกันที่นี่ใน Stackoverflow ฉันหวังว่าจะยืนยันแนวทางปฏิบัติที่ดีที่สุดสำหรับสถานการณ์นี้
Marklar

-2

คุณควรเพิ่มthenฟังก์ชั่นให้กับอินสแตนซ์ Promiseจะรับรู้ว่ามันเป็นวัตถุที่สามารถลบได้Promise.resolveโดยอัตโนมัติ

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)จะไม่ทำงานตามที่thisเป็นอยู่แล้ว สิ่งนี้นำไปสู่การแก้ไขแบบเรียกซ้ำไม่สิ้นสุด
Bergi
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.