トップレベルで非同期/待機を使用するにはどうすればよいですか?


184

私は以上続けられている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()after を使用せずに関数内で返された値を使用できる方法はありmain()ますか?


4
いいえ、非同期コードを同期化できるのはタイムマシンだけです。awaitpromise then構文の砂糖にすぎません。
Bergi、2017年

mainが値を返すのはなぜですか?もしそうなら、おそらくそれはエントリー・ポイントではなく、別の関数(例えば、非同期IIFE)から呼び出す必要があります。
Estus Flask 2017

@estusこれは、ノードで物事をテストしている間の単なる関数名であり、必ずしもプログラムの代表ではありませんでしたmain
Felipe

2
FYI async/awaitはES7ではなくES2017の一部です(ES2016)
Felix Kling

回答:


269

なぜこれが機能しないのか、頭を抱えているようには見えません。

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。他には何も行われないので、約束の拒否/非同期例外を処理する必要あります。それらを渡す呼び出し元はありません。必要に応じて、catchtry/ catch構文ではなく)関数を介して呼び出した結果に対してそれを行うことができます。

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

...これはもう少し簡潔です(そのため、私は気に入っています)。

または、もちろん、エラーを処理せず、「未処理の拒否」エラーのみを許可します。

#2- thencatch

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関数ラッパー(上記の#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関数のように)。プロミスが確定するまで待機してから、それに依存するモジュールの本体を評価します。


これを約束として考えると、関数がすぐに戻る理由が説明されます。トップレベルの匿名非同期関数を作成して実験したところ、今では意味のある結果が得られました
Felipe

2
@Felipe:はい、async/ await約束(砂糖の良い種類:-))を中心にシンタックスシュガーです。あなたはそれを約束を返すと考えているだけではありません。実際にそうです。(詳細
TJクラウダー

1
@LukeMcGregor- async最初にall- オプションを使用して、上記の両方を示しました。トップレベルの関数については、どちらの方法でも確認できます(主にasyncバージョンの2レベルのインデントのため)。
TJクラウダー

3
@Felipe-トップレベルのawaitプロポーザルがステージ3に達したので、答えを更新しました:-)
TJクラウダー

1
@SurajShrestha-いいえ。しかし、問題はありません。:-)
TJ

7

トップレベルawaitがステージ3に移動したため、質問への回答トップレベルで非同期/待機を使用するにはどうすればよいですか?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でしか利用できないことに注意してください。

あなたがいる場合は活字体を使用して、それが3.8に上陸しました

v8ではモジュールのサポート追加されました

(ゴンザロバハモンデスによるコメントのように)それはデノによってもサポートされています。


かなりクール。我々は、ノード実装のための任意のロードマップ持っていますか
フェリペ

わからないが、TypeScriptとBabelの実装が間もなく実現する可能性は非常に高い。TypeScriptチームには、ステージ3言語機能を実装するポリシーがあり、Babelプラグインは通常、TC39プロセスの一部としてビルドされ、提案をテストします。github.com/Microsoft/TypeScript/issues/…を
Taro

それはあまりにも電王で利用可能です(ただjsは、typescriptですが、まだそれをサポートしていませんgithub.com/microsoft/TypeScript/issues/25988deno.land 参照deno.news/issues/...
ゴンザロバハ

SyntaxError:awaitは非同期関数でのみ有効です
Sudipta Dhara

4

この問題の実際の解決策は、別の方法で解決することです。

おそらくあなたの目標は、通常はアプリケーションのトップレベルで発生する何らかの初期化です。

解決策は、アプリケーションの最上位レベルにJavaScriptステートメントが1つだけであることを確認することです。アプリケーションの上部にステートメントが1つしかない場合は、どこでも他のすべてのポイントでasync / awaitを自由に使用できます(もちろん、通常の構文規則に従います)。

別の言い方をすれば、トップレベル全体を関数でラップして、それがトップレベルではなくなり、アプリケーションのトップレベルで非同期/待機を実行する方法の問題を解決します。

アプリケーションの最上位は次のようになります。

import {application} from './server'

application();

1
私の目標は初期化です。データベース接続、データプルなどのこと。場合によっては、アプリケーションの残りの部分に進む前にユーザーのデータを取得する必要がありました。基本的application()に、非同期であることを提案していますか?
フェリペ

1
いいえ、私は単に、アプリケーションのルートにJavaScriptステートメントが1つしかない場合は問題がなくなることを示しています。表示されている最上位のステートメントは非同期ではありません。問題は、トップレベルでasyncを使用できないことです。そのレベルで実際に待機するのを待つことはできません。したがって、トップレベルにステートメントが1つしかない場合は、その問題を回避しています。インポートされた一部のコードで初期化非同期コードが停止しているため、非同期は問題なく機能し、アプリケーションの開始時にすべてを初期化できます。
デューク

1
修正-アプリケーションは非同期関数です。
デューク

4
すみません、すみません。ポイントは、通常、最上位レベルでは、非同期関数は待機しないということです。JavaScriptは次のステートメントに直接進むため、initコードが完了したことを確認できません。アプリケーションの最上部にステートメントが1つしかない場合、それは問題ではありません。
デューク

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

注意すべき主なことは、結果の関数は非同期関数ではないということです。したがって、そのawait中で直接使用することはできません。

しかし、このファイルでpromiseを処理する必要があるとすると、2つの方法が考えられます。

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

個人的には、それがasync機能する場合、node.jsがデフォルトでコードを関数に連結することを願っています。それはこの頭痛を取り除くでしょう。


0

トップレベルの待機は、来たるEcmaScript標準の機能です。現在、TypeScript 3.8(現時点ではRCバージョン)で使用できます。

TypeScript 3.8のインストール方法

次のコマンドを使用してnpmからインストールすることで、TypeScript 3.8の使用を開始できます。

$ npm install typescript@rc

現時点では、rcタグを追加して最新のtypescript 3.8バージョンをインストールする必要があります。


しかし、それを使用する方法を説明する必要がありますか?
raarts

-2

main()非同期で実行されるため、promiseを返します。then()メソッドで結果を取得する必要があります。また、then()promiseも返されるためprocess.exit()、プログラムを終了するために呼び出す必要があります。

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

2
違う。すべてのpromiseが承認または拒否され、メインスレッドでコードが実行されなくなると、プロセスは自動的に終了します。

@Dev:通常exit()、エラーが発生したかどうかを通知するために別の値を渡します。
9000

9000 @はい、それここで行われ、0の終了コードがデフォルトであるため、それを含める必要はありませんされていません

@ 9000実際には、エラーハンドラーはおそらく使用する必要がありますprocess.exit(1)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.