JavaScript の async/await を理解する

高洛峰
リリース: 2016-11-21 16:14:08
オリジナル
1275 人が閲覧しました

async と await は何をしているのでしょうか?

どの名前にも意味があります。文字通りに理解しましょう。 async は「asynchronous」の略で、await は async wait の略と考えることができます。したがって、async は関数が非同期であることを宣言するために使用され、await は非同期メソッドの実行が完了するのを待つために使用されることをよく理解する必要があります。

興味深い文法規則もあります。await は非同期関数でのみ使用できます。それでは、注意深い友人は、await が非同期関数でのみ使用できる場合、この非同期関数はどのように呼び出すべきかという疑問を持つでしょう。

await を通じて非同期関数を呼び出す必要がある場合は、この呼び出しを別の非同期関数でラップする必要があります。その後、無限ループに入り、そこから抜け出すことはできません...

async 関数が await を必要としない場合それでは、async はどのような役割を果たしますか?

async は何をしますか?

この質問の鍵は、async 関数が戻り値をどのように処理するかです。

もちろん、return ステートメントを通じて直接欲しい値を返すことができればいいのですが、その場合は await を使う必要はないようです。それで、コードを書いて、それが何を返すかを試してみてください:

async function testAsync() {
    return "hello async";
}

const result = testAsync();
console.log(result);
ログイン後にコピー

出力を見たとき、出力が Promise オブジェクトであることに突然気づきました。

c:\var\test> node --harmony_async_await .
Promise { 'hello async' }
ログイン後にコピー

つまり、async 関数は Promise オブジェクトを返します。この情報はドキュメントからも入手できます。非同期関数 (関数ステートメント、関数式、ラムダ式を含む) は Promise オブジェクトを返します。関数内で直接値が返された場合、async は Promise.resolve() を通じてその直接値を Promise オブジェクトにカプセル化します。

async 関数は Promise オブジェクトを返すので、最外層が戻り値を取得するために await を使用できない場合は、もちろん、元の方法を使用する必要があります: then() チェーンを使用してこの Promise オブジェクトを処理します。このように

testAsync().then(v => {
    console.log(v);    // 输出 hello async
});
ログイン後にコピー

さあ見てください振り返って考えてみてください。async 関数が値を返さなかったらどうなるでしょうか? Promise.resolve(unknown) を返すことは容易に想像できます。

Promise の特性について考えてみましょう - 待機がないため、await なしで async 関数を実行すると、すぐに実行され、Promise オブジェクトが返され、後続のステートメントがブロックされることはありません。これは、Promise オブジェクトを返す通常の関数と何ら変わりません。

次に重要なポイントは await キーワードです。

await は何を待っているのですか? 一般的に、await は非同期関数が完了するのを待っていると考えられています。ただし、構文によれば、await は式を待っており、この式の評価結果は Promise オブジェクトまたはその他の値になります (つまり、特別な制限はありません)。

async 関数は Promise オブジェクトを返すため、await を使用して async 関数の戻り値を待つことができます。これは await が async 関数を待っているとも言えますが、実際に待っていることを明確にする必要があります戻り値の場合。 await は Promise オブジェクトを待機するために使用されるだけでなく、任意の式の結果を待機することもできるので、実際には await の後に通常の関数呼び出しまたは直接量を続けることができることに注意してください。したがって、次の例は正しく実行できます。

function getSomething() {
    return "something";
}

async function testAsync() {
    return Promise.resolve("hello async");
}

async function test() {
    const v1 = await getSomething();
    const v2 = await testAsync();
    console.log(v1, v2);
}

test();
ログイン後にコピー

await は、待機したいものまで待機します。その後、

await が待機したいもの、Promise オブジェクト、またはその他の値を待機すると何が起こるでしょうか。最初に言っておきますが、await は式を形成するために使用される演算子です。await 式の結果は、何を待っているかによって異なります。

待機しているものが Promise オブジェクトではない場合、await 式の結果が待機しているものになります。

如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。

看到上面的阻塞一词,心慌了吧……放心,这就是 await 必须用在 async 函数中的原因。async 函数调用不会造成阻塞,它内部所有的阻塞都被封装在一个 Promise 对象中异步执行。

async/await 帮我们干了啥

作个简单的比较

上面已经说明了 async 会将其后的函数(函数表达式或 Lambda)的返回值封装成一个 Promise 对象,而 await 会等待这个 Promise 完成,并将其 resolve 的结果返回出来。

现在举例,用 setTimeout 模拟耗时的异步操作,先来看看不用 async/await 会怎么写

function takeLongTime() {
    return new Promise(resolve => {
        setTimeout(() => resolve("long_time_value"), 1000);
    });
}

takeLongTime().then(v => {
    console.log("got", v);
});
ログイン後にコピー

如果改用 async/await 呢,会是这样

function takeLongTime() {
    return new Promise(resolve => {
        setTimeout(() => resolve("long_time_value"), 1000);
    });
}

async function test() {
    const v = await takeLongTime();
    console.log(v);
}

test();
ログイン後にコピー

眼尖的同学已经发现 takeLongTime() 没有申明为 async。实际上,takeLongTime() 本身就是返回的 Promise 对象,加不加 async 结果都一样,如果没明白,请回过头再去看看上面的“async 起什么作用”。

又一个疑问产生了,这两段代码,两种方式对异步调用的处理(实际就是对 Promise 对象的处理)差别并不明显,甚至使用 async/await 还需要多写一些代码,那它的优势到底在哪?

async/await 的优势在于处理 then 链

单一的 Promise 链并不能发现 async/await 的优势,但是,如果需要处理由多个 Promise 组成的 then 链的时候,优势就能体现出来了(很有意思,Promise 通过 then 链来解决多层回调的问题,现在又用 async/await 来进一步优化它)。

假设一个业务,分多个步骤完成,每个步骤都是异步的,而且依赖于上一个步骤的结果。我们仍然用 setTimeout 来模拟异步操作:

/**
 * 传入参数 n,表示这个函数执行的时间(毫秒)
 * 执行的结果是 n + 200,这个值将用于下一步骤
 */
function takeLongTime(n) {
    return new Promise(resolve => {
        setTimeout(() => resolve(n + 200), n);
    });
}

function step1(n) {
    console.log(`step1 with ${n}`);
    return takeLongTime(n);
}

function step2(n) {
    console.log(`step2 with ${n}`);
    return takeLongTime(n);
}

function step3(n) {
    console.log(`step3 with ${n}`);
    return takeLongTime(n);
}
ログイン後にコピー

现在用 Promise 方式来实现这三个步骤的处理

function doIt() {
    console.time("doIt");
    const time1 = 300;
    step1(time1)
        .then(time2 => step2(time2))
        .then(time3 => step3(time3))
        .then(result => {
            console.log(`result is ${result}`);
            console.timeEnd("doIt");
        });
}

doIt();

// c:\var\test>node --harmony_async_await .
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
// doIt: 1507.251ms
ログイン後にコピー

输出结果 result 是 step3() 的参数 700 + 200 = 900。doIt() 顺序执行了三个步骤,一共用了 300 + 500 + 700 = 1500 毫秒,和 console.time()/console.timeEnd() 计算的结果一致。

如果用 async/await 来实现呢,会是这样

async function doIt() {
    console.time("doIt");
    const time1 = 300;
    const time2 = await step1(time1);
    const time3 = await step2(time2);
    const result = await step3(time3);
    console.log(`result is ${result}`);
    console.timeEnd("doIt");
}

doIt();
ログイン後にコピー

结果和之前的 Promise 实现是一样的,但是这个代码看起来是不是清晰得多,几乎跟同步代码一样

还有更酷的

现在把业务要求改一下,仍然是三个步骤,但每一个步骤都需要之前每个步骤的结果。

function step1(n) {
    console.log(`step1 with ${n}`);
    return takeLongTime(n);
}

function step2(m, n) {
    console.log(`step2 with ${m} and ${n}`);
    return takeLongTime(m + n);
}

function step3(k, m, n) {
    console.log(`step3 with ${k}, ${m} and ${n}`);
    return takeLongTime(k + m + n);
}
ログイン後にコピー

这回先用 async/await 来写:

async function doIt() {
    console.time("doIt");
    const time1 = 300;
    const time2 = await step1(time1);
    const time3 = await step2(time1, time2);
    const result = await step3(time1, time2, time3);
    console.log(`result is ${result}`);
    console.timeEnd("doIt");
}

doIt();

// c:\var\test>node --harmony_async_await .
// step1 with 300
// step2 with 800 = 300 + 500
// step3 with 1800 = 300 + 500 + 1000
// result is 2000
// doIt: 2907.387ms
ログイン後にコピー

除了觉得执行时间变长了之外,似乎和之前的示例没啥区别啊!别急,认真想想如果把它写成 Promise 方式实现会是什么样子?

function doIt() {
    console.time("doIt");
    const time1 = 300;
    step1(time1)
        .then(time2 => {
            return step2(time1, time2)
                .then(time3 => [time1, time2, time3]);
        })
        .then(times => {
            const [time1, time2, time3] = times;
            return step3(time1, time2, time3);
        })
        .then(result => {
            console.log(`result is ${result}`);
            console.timeEnd("doIt");
        });
}

doIt();
ログイン後にコピー

有没有感觉有点复杂的样子?那一堆参数处理,就是 Promise 方案的死穴

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート