Javascript->async、awaitの探索


asyncを話す前にpromiseを簡単に話してください.まず、多くの人の一般的な誤った観点を修正します-->'promiseは非同期です'、コードを見てください.
console.log(1);
let p1 = new Promise(r => { console.log(2); r() });
p1.then(_ => console.log(3));
console.log(4);
let p2 = new Promise(r => { console.log(5); r() });
p2.then(_ => console.log(6));
console.log(7);
//    1 2 4 5 7 3 6

印刷結果から、promiseが同期していると断定できます.では、promiseは同期していると言っています.thenは非同期です.いいえ、簡単に原因を言います:まず結果を言います:promiseのthenも同期しています.このように出力されるのは、new promise(fn)fnのr()関数が非同期でスレッドが保留され、thenに実行されると、thenのコードブロックがすぐに実行を開始し(注意して実行を開始する)、成功したコールバック関数をresovledCallbacksに入れただけで、状態修正がfulfiledに完了した場合でも、上の実行then(fn)のfn内のコード実行は非同期で動作します.すぐにconsoleを実行するわけではありません.thenの内部実装方式はpromisa仕様に従ってsettimeoutが遅延器の内部でaaaを実行するので、thenメソッドは同期関数を肯定しますが、実際には永遠に非同期です.2つのsettimeoutは非同期で成功または失敗したコールバック関数を実行することを保証しているからです.具体的にはr()内部に遅延実行コールバックが設定されており、遅延settimeoutの最小値、すなわちrこそ非同期である.
console.log(1);
let p1 = new Promise(r => { console.log(2); r() });
p1.then(console.log(3));
console.log(4);
let p2 = new Promise(r => { console.log(5); r() });
p2.then(console.log(6));
console.log(7);

//    1 2 3 4 5 6 7  

分かったでしょう?解決された問題は主に以下のようにまとめることができます.
  • コールバック地獄、コードのメンテナンスが困難で、しばしば最初の関数の出力が2番目の関数の入力であるという現象
  • promiseは、複数の同時要求をサポートし、同時要求のデータ
  • を取得することができる.
  • promiseは非同期の問題を解決することができて、自身はpromiseが非同期の
  • だとは言えません
    promiseはもうあまり紹介しません.時間があれば、もっと深く研究してもいいです.promiseは本当にcallback hellを完全に解決しましたか.まず1つのシーンには4つの関数があり、順番に実行する必要があります.つまり、前のpromise fullが実行されるまで待たなければなりません.後のpromiseは、前のpromiseで返される値です.たとえば、
    function f1() {
        return new Promise(resolve => {
            setTimeout(_ => resolve('f1'), 500)
        })
    }
    
    function f2(params) {
        return new Promise(resolve => {
            console.log(params);
            setTimeout(_ => resolve(params + 'f2'), 500)
        })
    }
    
    function f3(params) {
        return new Promise(resolve => {
            console.log(params);
            setTimeout(_ => resolve(params + 'f3'), 500)
        })
    }
    
    function f4(params) {
        return new Promise(resolve => {
            console.log(params);
            setTimeout(_ => resolve(params + 'f4'), 500)
        })
    }
    

    私たちは普通このように書きます.
    f1().then(res => {
        return f2(res)
    }).then(res => {
        return f3(res)
    }).then(res => {
        return f4(res)
    });

    あるいはもう少し簡素化します
    f1().then(f2).then(f3).then(f4);

    美しく見えますが、第1の方法で書かず、第2の方法で書くと、可読性が悪いことがわかります.f 1()だけを見てみましょう.then(f2).then(f3).then(f4);このコードは実はf 1,f 2,f 3,f 4が何のつながりがあるのか全く見えず、f 2,f 3,f 4も入力として上位層の出力を使っているので、最も理想的な表現だと思います.
    f1();
    f2();
    f3();
    f4();

    しかし、もしそうなら、私たちの関数が順次実行されていることは保証できません.入出力はおろか.こうして、私たちのasyncが登場して、あなたはこのように書くことができます.
    void (async function() {
        let r1 = await f1()
        let r2 = await f2(r1)
        let r3 = await f3(r2)
        await f4(r3)
    })();

    ES 7が提案したasync関数は、JavaScriptが非同期操作に対して究極の解決策をもたらした.No more callback hell.async関数はGenerator関数のシンタックス糖である.キーワードasyncを使用して表し、関数内部でawaitを使用して非同期を表します.Generatorに比べて、Async関数の改善は次の4つの点にあります(この4つのセグメントは私が他の場所で見つけたもので、まとめもいいです):
  • にはアクチュエータが内蔵されています.Generator関数の実行はアクチュエータに依存しなければならないが、Aysnc関数はアクチュエータを持参し、呼び出し方式は通常の関数の呼び出しと同じ
  • である.
  • より良い意味.asyncとawaitは*とyieldよりも意味化されている
  • より広い適用性.Coモジュールはyieldコマンドの後ろにThunk関数またはPromiseオブジェクトしかないことを約束している.一方、async関数のawaitコマンドの後には、Promiseまたは元のタイプの値(Number,string,boolean)
  • があります.
  • の戻り値はPromiseです.async関数の戻り値はPromiseオブジェクトであり、Generator関数が返すIteratorオブジェクトよりも便利であり、then()メソッドを直接使用して呼び出すことができる(coモジュールは実際にはGeneratorとPromiseを結合し、自動的にGeneratorを実行する)
  • .
    asynch関数にも戻り値があり、promiseオブジェクトなので使用できます.then
    async function f() {
        return 1
    }
    console.log(f())     // Promise { 1 }

    ただし、関数の実行中にawaitに遭遇すると先に戻ってくるので注意してください.
    async function f() {
        await 2;
        return 1
    }
    console.log(f()); // Promise {  }
    

    私のコードのreutnr 1ですが、結果はpending状態のPromiseオブジェクトを返します.async関数内部のreturn文が返す値はthenメソッドコールバック関数のパラメータになります.
    async function f() {
        await 2;
        return 1
    }
    f().then(res => {
        console.log(res) // 1
    })

    注意すべきは、私はずっと関数の実行を強調しています.awaitは実行を待つという意味ですが、外部に副作用を与えることはありません.
    async function f() {
        await 2;
        console.log('a')
        return 1
    }
    f()
    console.log('b') // b  a      

    印刷結果から、プログラム実行中にawaitに遭遇したが、外部のコード実行をブロックしていないことを見たので、Javascriptの非同期の本質は変わっていないが、少なくともasync関数で私たちの流れをよくコントロールすることができ、この文法糖の強さを見てみましょう.ターゲット:ajaxリクエストを30回送信し、リクエストがシリアルであることを要求する(すなわち、リクエストを送信する際に前のリクエストresを待たなければならない)この問題を従来のpromisepromise.thenの方法は実装が困難であるため,まずajaxリクエストをシミュレートし,各ajaxのtimeresponseが400 msであると仮定した.
    function ajax(n) {
        return new Promise((rs, rj) => {
            setTimeout(() => {
                console.log(n);
                rs()
            }, 400)
        })
    }
    

    Promise実装:
    let n = 50
    let task = ajax(n);
    function run() {
        task.then(_ => {
            --n && (task = ajax(n)) && run()
        })
    }
    run();    

    Generator実装
    let num = 50;
    
    function* Ge() {
        while (true) {
            yield ajax(num)
        }
    }
    let re = Ge()
    function run() {
        let result = re.next().value
        result.then(_ => {
            num-- && run()
        })
    }
    run()  

    async実装
    let n = 50
    async function run() {
        while (n--) await ajax(n)
    }
    run()
    

    比較してみると、さっきasyncが実はGeneratorの文法糖だと言ったのは明らかだ.asyncがどのように実現する原理なのか、理解したいのか、ジェネレータ(generator)を勉強しなければならない人もいるに違いない.結局asyncはgeneratorの文法糖にすぎないので、それをスキップして直接asyncを勉強するのはもちろん多くのことを見逃します.asyncはGenerator+オートアクチュエータに等しい.話は前の例に戻る
          void (async function() {
            let r1 = await f1()
            let r2 = await f2(r1)
            let r3 = await f3(r2)
            await f4(r3)
        })();

    asyncでawaitに遭遇すると後のPromiseの戻り結果(同期を除く)を待つと言っていたので、上記のコードの実行順序はf 1->f 2->f 3なので、f 1,2,3を同時に実行させるにはどうすればいいのでしょうか.Promiseが同期していることを知っています.new Promise(...)というときには、事実上すでに実行が始まっていますが、結果がバンド状態のPに戻るだけなので、f 1,2,3を並行させたいなら仕方ありません
    void (async function() {
        let r1 = new Promise(...)
        let r2 = new Promise(...)
        let r3 = new Promise(...)
        await r1
        await r2
        await r3
    })();        

    これはnew Promiseのコードブロックが同時に行われていることに相当し,状態がpendingからfullに変わる時間の長さはビジネスニーズや場合によって決定され,もう一つの方法はより直感的である可能性がある.
    void (async function() {
        let re = await Promise.all([p1, p2, p3])
    })();  

    ここでreは1つの配列であり、値はそれぞれp 1,2,3に対応する.raceに変えてもいいよrace([p1, p2, p3])
    リクエストが多ければmap,foreachを用いて並列に実行することもできます
    function plist(n) {
        return new Promise(resolve => {
            console.log('start:' + n)
            setTimeout(_ => {
                resolve(n)
            }, 2000)
        })
    }
    
    let c = [...new Array(100).keys()]
    let pros = c.map(async n => {
        return await plist(n)
    })
    for (let p of pros) {
        p.then(res => console.log('end:' + res))
    }

    mapとforEachは並行してpromise配列を実行するが,for-in for-of forはシリアルである.この2つの点を知って、私たちは多くの非同期要求を効率的に処理することができます.最後に簡単に言えばasyncのエラー処理方法promiseでの異常やrejectはtry catchではキャプチャできないことを知っています.例えば
    try {
        Promise.reject('an normal error')
    } catch (e) {
        console.log('error comming')
        console.log(e)
    }

    このエラーtry catchは取得できません.function fn(){
        try {
            new Promise(resolve => {
                JSON.parse(a)
                resolve()
            })
        } catch (e) {
            console.log('error comming')
            console.log(e)
        }
    }
    
    fn()

    ここでReferenceError異常を直接投げ出してasyncに入れます
    async function fn() {
        try {
            await new Promise(resolve => {
                JSON.parse(a)
                resolve()
            })
        } catch (e) {
            console.log('error comming')
            console.log(e)
        }
    }
    
    fn()    

    不思議なことに、異常が捕獲されたが、実はこの場所は私もその本当の原因を確定していない.重点はawaitが何をしたのか、実行環境にどのような影響を与えたのか、まず私の観点を述べる.promiseは非ブロックである.つまりpromise外部のtry、catchにとって、内部のpromiseは非同期実行である.try cathchは非同期エラーをキャプチャできませんが、awaitはpromiseの実行を待つことを示し、この約束の実行結果を待つことを示し、現在のasync実行環境のコード実行を一時停止します.つまり、asyncの下で、awaitは同期的で、ブロックされていると考えることができます.このエラーは同期放出(await new Promise(...)であると考えられます投げ出したので捕獲されます.しかし、asyncの異常をキャプチャする方法はお勧めしません.1つはコード構造が混乱しているように見えます.2つはtry/catchのcatch部分に異常があれば、どうすればいいですか.だからasync()を使うことをお勧めします.catchは、asyncが戻り値の有無にかかわらずpromiseオブジェクトを返すため処理します.
    async function fn() {
    
    }
    console.log(fn().then)    // [Function ...]

    asyncはreturnを使用してpromiseを返すこともできます
    async function fn() {
        // return await Promise.resolve(1)
        // return Promise.resolve(1)
    }

    asyncについて簡単に紹介します.次の文章では、なぜ私たちがGeneratorを使うのが少ないのか、あるいはなぜasyncがGeneratorの文法糖なのかを説明します.