ฉันจะใช้ async / คอยอยู่ที่ระดับสูงสุดได้อย่างไร


182

ฉันได้ไปมากกว่าasync/ awaitและหลังจากอ่านบทความต่าง ๆ ฉันตัดสินใจทดสอบด้วยตนเอง อย่างไรก็ตามฉันไม่สามารถคาดศีรษะได้ว่าทำไมสิ่งนี้ถึงไม่ทำงาน:

async function main() {  
    var value = await Promise.resolve('Hey there');
    console.log('inside: ' + value);
    return value;
}

var text = main();  
console.log('outside: ' + text);

คอนโซลเอาต์พุตต่อไปนี้ (โหนด v8.6.0):

> ข้างนอก: [วัตถุสัญญา]

> ข้างใน: เฮ้นั่น

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

มีวิธีที่ฉันสามารถใช้ค่าที่ส่งกลับภายในฟังก์ชั่นโดยไม่ต้องใช้ความ.then()หลังmain()?


4
ไม่ได้เครื่องเวลาเท่านั้นที่สามารถสร้างรหัสซิงโครนัสได้ awaitไม่มีอะไรนอกจากน้ำตาลสำหรับthenไวยากรณ์สัญญา
Bergi

ทำไมหลักถึงส่งคืนค่า หากเป็นไปได้อาจไม่ใช่จุดเริ่มต้นและจำเป็นต้องเรียกใช้โดยฟังก์ชันอื่น (เช่น async IIFE)
Estus Flask

@estus มันเป็นเพียงชื่อฟังก์ชั่นด่วนในขณะที่ฉันกำลังทดสอบสิ่งต่าง ๆ ในโหนดไม่จำเป็นต้องเป็นตัวแทนของโปรแกรมmain
Felipe

2
FYI async/awaitเป็นส่วนหนึ่งของ ES2017 ไม่ใช่ ES7 (ES2016)
Felix Kling

คำตอบ:


267

ฉันไม่สามารถคาดศีรษะได้ว่าทำไมสิ่งนี้ถึงไม่ได้

เพราะmainคืนสัญญา asyncฟังก์ชั่นทั้งหมดทำ

ที่ระดับบนสุดคุณต้อง:

  1. ใช้asyncฟังก์ชั่นระดับสูงสุดที่ไม่เคยปฏิเสธ (เว้นแต่คุณต้องการข้อผิดพลาด "ปฏิเสธการจัดการ") หรือ

  2. ใช้thenและcatchหรือ

  3. (เร็ว ๆ นี้!)ใช้ระดับสูงสุดawaitข้อเสนอที่มาถึงขั้นตอนที่ 3 ในกระบวนการที่อนุญาตให้ใช้ระดับบนสุดของawaitในโมดูล

# 1 - asyncฟังก์ชั่นระดับบนสุดที่ไม่เคยปฏิเสธ

(async () => {
    try {
        var text = await main();
        console.log(text);
    } catch (e) {
        // Deal with the fact the chain failed
    }
})();

แจ้งให้ทราบล่วงหน้าcatch; คุณต้องจัดการข้อยกเว้นการปฏิเสธ / async ตามสัญญาเนื่องจากไม่มีสิ่งใดเกิดขึ้น คุณไม่มีผู้โทรเพื่อส่งต่อให้ หากคุณต้องการคุณสามารถทำเช่นนั้นกับผลลัพธ์ของการโทรผ่านcatchฟังก์ชัน (แทนtry/ catchไวยากรณ์):

(async () => {
    var text = await main();
    console.log(text);
})().catch(e => {
    // Deal with the fact the chain failed
});

... ซึ่งกระชับกว่านี้เล็กน้อย (ฉันชอบด้วยเหตุผลนั้น)

หรือแน่นอนไม่จัดการข้อผิดพลาดและอนุญาตให้เกิดข้อผิดพลาด "ปฏิเสธการจัดการ"

# 2 - thenและcatch

main()
    .then(text => {
        console.log(text);
    })
    .catch(err => {
        // Deal with the fact the chain failed
    });

catchจัดการจะถูกเรียกว่าข้อผิดพลาดเกิดขึ้นในห่วงโซ่หรือในของคุณthenจัดการ (ตรวจสอบให้แน่ใจว่าcatchตัวจัดการของคุณไม่โยนข้อผิดพลาดเนื่องจากไม่มีการลงทะเบียนเพื่อจัดการกับพวกเขา)

หรือทั้งสองข้อโต้แย้งไปที่then:

main().then(
    text => {
        console.log(text);
    },
    err => {
        // Deal with the fact the chain failed
    }
);

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

# 3 ระดับสูงสุดawaitในโมดูล

คุณไม่สามารถใช้awaitที่ระดับสูงสุดของสคริปต์ที่ไม่ใช่โมดูล แต่ข้อเสนอระดับบนสุดawait ( ขั้นตอนที่ 3 ) ช่วยให้คุณสามารถใช้งานได้ที่ระดับบนสุดของโมดูล มันคล้ายกับการใช้asyncฟังก์ชั่น wrapper ระดับบนสุด(# 1 ด้านบน) ในที่ที่คุณไม่ต้องการให้รหัสระดับบนสุดของคุณที่จะปฏิเสธ (โยนข้อผิดพลาด) เพราะมันจะทำให้เกิดข้อผิดพลาดการปฏิเสธการจัดการ ดังนั้นหากคุณไม่ต้องการให้มีการปฏิเสธที่ไม่สามารถจัดการได้เมื่อสิ่งต่าง ๆ เกิดข้อผิดพลาดเช่นเดียวกับ # 1 คุณต้องล้อมโค้ดของคุณไว้ในเครื่องมือจัดการข้อผิดพลาด:

// In a module, once the top-level `await` proposal lands
try {
    var text = await main();
    console.log(text);
} catch (e) {
    // Deal with the fact the chain failed
}

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


การคิดตามสัญญาอธิบายตอนนี้ว่าทำไมฟังก์ชันส่งคืนทันที ฉันได้ทดลองทำฟังก์ชั่น async แบบไม่ระบุชื่อระดับบนสุดและได้ผลลัพธ์ที่เหมาะสม
เฟลิเป้

2
@Felipe: ใช่async/ awaitเป็นน้ำตาลประโยครอบ ๆ สัญญา (น้ำตาลชนิดที่ดี :-)) คุณไม่ได้แค่คิดว่าเป็นการคืนสัญญา มันทำจริง ( รายละเอียด )
TJ Crowder

1
@LukeMcGregor - ฉันแสดงทั้งสองข้างต้นพร้อมasyncตัวเลือกทั้งหมดก่อน สำหรับฟังก์ชั่นระดับบนสุดฉันสามารถเห็นได้ทั้งสองทาง (ส่วนใหญ่เป็นเพราะการเยื้องสองระดับในasyncเวอร์ชั่น)
TJ Crowder

3
@Felipe - ฉันได้อัปเดตคำตอบแล้วในขณะนี้ว่าawaitข้อเสนอระดับบนสุดได้มาถึง
TJ Crowder

1
@SurajShrestha - ไม่ แต่มันไม่ใช่ปัญหาที่มันไม่ :-)
TJ Crowder

7

ระดับบนสุดawaitย้ายไปยังขั้นตอนที่ 3 ดังนั้นคำตอบสำหรับคำถามของคุณฉันจะใช้ async / รอคอยที่ระดับสูงสุดได้อย่างไร คือการเพิ่มawaitการเรียกไปยังmain():

async function main() {  
    var value = await Promise.resolve('Hey there');
    console.log('inside: ' + value);
    return value;
}

var text = await main();  
console.log('outside: ' + text)

หรือเพียงแค่:

const text = await Promise.resolve('Hey there');
console.log('outside: ' + text)

โปรดจำไว้ว่ามันยังคงมีอยู่ในWebpack@v5.0.0-alpha.15เท่านั้น

หากคุณกำลังใช้ typescriptก็เป็นเจ้าของที่ดินใน 3.8

v8 ได้เพิ่มการสนับสนุนในโมดูล

มันสนับสนุนโดยDeno (ตามความเห็นของ gonzalo-bahamondez)


สวยเท่ห์ เรามีแผนงานใด ๆ สำหรับการดำเนินงานโหนด
Felipe

ไม่ทราบ แต่มีโอกาสมากที่เราจะเห็นการใช้งาน TypeScript และ Babel เร็ว ๆ นี้ ทีม TypeScript มีนโยบายในการใช้งานคุณสมบัติภาษาระยะที่ 3 และปลั๊กอิน Babel มักจะถูกสร้างขึ้นเป็นส่วนหนึ่งของกระบวนการ TC39 เพื่อทดสอบข้อเสนอ ดูgithub.com/Microsoft/TypeScript/issues/…
เผือก

มันมีให้ใน deno ด้วย (แค่ js, typescript ยังไม่รองรับgithub.com/microsoft/TypeScript/issues/25988 ) deno.land ดูdeno.news/issues/ ….
Gonzalo Bahamondez

SyntaxError: การรอคอยใช้ได้เฉพาะในฟังก์ชัน async
Sudipta Dhara

4

ทางออกที่แท้จริงของปัญหานี้คือแนวทางที่แตกต่าง

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

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

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

นี่คือลักษณะระดับสูงสุดของใบสมัครของคุณ:

import {application} from './server'

application();

1
คุณถูกต้องว่าเป้าหมายของฉันคือการเริ่มต้น สิ่งต่าง ๆ เช่นการเชื่อมต่อฐานข้อมูลดึงข้อมูลเป็นต้นในบางกรณีจำเป็นต้องได้รับข้อมูลของผู้ใช้ก่อนดำเนินการกับส่วนที่เหลือของแอปพลิเคชัน เป็นหลักคุณกำลังเสนอที่application()async?
เฟลิเป้

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

1
การแก้ไข - แอปพลิเคชันเป็นฟังก์ชัน async
Duke Dougal

4
ฉันไม่ชัดเจนขอโทษ ประเด็นก็คือโดยปกติแล้วที่ระดับบนสุดฟังก์ชั่น async ไม่รอ ... จาวาสคริปต์ตรงไปที่คำสั่งถัดไปดังนั้นคุณจึงไม่สามารถมั่นใจได้ว่ารหัส init ของคุณเสร็จสมบูรณ์แล้ว หากมีคำสั่งเดียวที่ด้านบนของใบสมัครของคุณที่ไม่สำคัญ
Duke Dougal

3

หากต้องการให้ข้อมูลเพิ่มเติมเกี่ยวกับคำตอบปัจจุบัน:

ขณะนี้เนื้อหาของnode.jsไฟล์ถูกต่อกันแบบสตริงเพื่อสร้างส่วนของฟังก์ชัน

ตัวอย่างเช่นหากคุณมีไฟล์test.js:

// Amazing test file!
console.log('Test!');

จากนั้นnode.jsจะเชื่อมฟังก์ชันที่มีลักษณะดังนี้:

function(require, __dirname, ... a bunch more top-level properties) {
  // Amazing test file!
  console.log('test!');
}

สิ่งสำคัญที่ควรทราบคือว่าฟังก์ชั่นที่เกิดขึ้นไม่ใช่ฟังก์ชั่น async ดังนั้นคุณจึงไม่สามารถใช้คำศัพท์awaitโดยตรงได้

แต่บอกว่าคุณต้องทำงานกับสัญญาในไฟล์นี้แล้วมีสองวิธีที่เป็นไปได้:

  1. อย่าใช้await โดยตรงภายในฟังก์ชั่น
  2. อย่าใช้ await

ตัวเลือก 1 ต้องการให้เราสร้างขอบเขตใหม่ (และขอบเขตนี้อาจเป็นasyncเพราะเราสามารถควบคุมได้):

// Amazing test file!
// Create a new async function (a new scope) and immediately call it!
(async () => {
  await new Promise(...);
  console.log('Test!');
})();

ตัวเลือกที่ 2 ต้องการให้เราใช้ API สัญญาเชิงวัตถุ (กระบวนทัศน์การทำงานที่ไม่ค่อยสวยเท่ากัน แต่ใช้งานได้กับสัญญา)

// Amazing test file!
// Create some sort of promise...
let myPromise = new Promise(...);

// Now use the object-oriented API
myPromise.then(() => console.log('Test!'));

โดยส่วนตัวฉันหวังว่าถ้ามันสามารถใช้งานได้ node.js จะโดยค่าเริ่มต้นเชื่อมรหัสเข้าสู่asyncฟังก์ชั่น ที่จะกำจัดอาการปวดหัวนี้


0

การรอระดับสูงสุดเป็นคุณสมบัติของมาตรฐาน EcmaScript ที่จะเกิดขึ้น ขณะนี้คุณสามารถเริ่มใช้กับ TypeScript 3.8 (ในรุ่น RC ได้ในขณะนี้)

วิธีการติดตั้ง TypeScript 3.8

คุณสามารถเริ่มใช้TypeScript 3.8 ได้โดยติดตั้งจาก npmโดยใช้คำสั่งต่อไปนี้:

$ npm install typescript@rc

ในตอนนี้คุณต้องเพิ่มrcแท็กเพื่อติดตั้ง typescript 3.8 รุ่นล่าสุด


แต่คุณจะต้องอธิบายวิธีการใช้งานก่อน?
raarts

-2

ตั้งแต่main()ทำงานแบบอะซิงโครนัสส่งคืนสัญญา คุณต้องได้รับผลลัพธ์ในthen()วิธีการ และเนื่องจากการthen()ส่งคืนสัญญาเช่นกันคุณต้องโทรprocess.exit()เพื่อสิ้นสุดโปรแกรม

main()
   .then(
      (text) => { console.log('outside: ' + text) },
      (err)  => { console.log(err) }
   )
   .then(() => { process.exit() } )

2
ไม่ถูกต้อง. เมื่อสัญญาทั้งหมดได้รับการยอมรับหรือถูกปฏิเสธและไม่มีการเรียกใช้โค้ดในเธรดหลักอีกต่อไปกระบวนการจะสิ้นสุดลงด้วยตนเอง

@Dev: โดยปกติคุณต้องการส่งผ่านค่าที่แตกต่างกันไปexit()ยังสัญญาณว่าเกิดข้อผิดพลาด
9000

@ 9000 ใช่ แต่นั่นไม่ได้ทำที่นี่และเนื่องจากรหัสทางออก 0 เป็นค่าเริ่มต้นจึงไม่จำเป็นต้องรวมไว้

@ 9000 อันที่จริงผู้จัดการข้อผิดพลาดน่าจะใช้process.exit(1)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.