ES 6—Asyncと非同期プログラミング(11)


シングルスレッドはJavascript言語の最も本質的な特性の一つで、Javascriptエンジンはjsコードを実行する時、同じ時間に単一のタスクしか実行できません.
このモードのメリットは実現が比較的簡単で、実行環境が比較的単純であることです.
悪いところは一つのタスクがある限り、時間がかかります.後のタスクは並んで待つ必要があります.プログラム全体の実行を遅らせます.よくあるブラウザは無応答(仮死)で、あるJavascriptコードが長時間運行されているため、ページカード全体がこの場所にあるため、他のタスクは実行できません.
ですから、非同期プログラミングはJavaScript言語にとって重要です.
一部の子供たちはまだ「非同期」をよく理解していないかもしれません.
「非同期」とは、一つの任務を二つの段階に分けて、最初の段階を実行して、他の任務を実行します.準備ができたら、また後ろに回って第二段を実行します.
例えば、ファイルを読み込んで処理するジョブがありますが、ジョブの最初のセグメントはオペレーティングシステムに要求し、ファイルの読み込みを要求します.その後、プログラムは他のタスクを実行し、オペレーティングシステムがファイルに戻ってから、タスクの第二段(処理ファイル)を実行します.このような不連続な実行は、非同期と呼ばれる.
したがって、連続して実行することを同期という.連続実行ですので、他のタスクを挿入できません.オペレーティングシステムがハードディスクからファイルを読み込むまでの間は、プログラムは待つしかありません.
話の通俗点:
朱自清の『後ろ姿』で、父は朱自清に対して言いました.「蜜柑をいくつか買いに行きます.ここにいます.歩かないでください.」
朱自清は歩いていません.ミカンを買った父が一緒にみかんを食べるのを待っています.同期といいます.
父を待たずに一人で行ったら、父と一緒にみかんを食べられない.
1、非同期プログラミング
私たちはユーザー登録という特に一般的なシーンを例にとって、非同期プログラミングについて説明します.
第一歩は、ユーザが登録されているかどうかを検証する.
第二ステップ、登録されていません.認証コードを送信します.
第三段階、検証コード、パスワードを記入し、検証コードが正しいかどうかを確認する.
この過程には一定の順序があります.前のステップが完成してこそ、次のステップがスムーズに進みます.
1.1コールバック関数
function testRegister(){}  //         
function sendMessage(){}   //         x
function testMessage(){}   //          

function doRegister(){  //    
    testRegister(data){
        if(data===false){ //   
            
        }else{ //   
             sendMessage(data){
                 if(data===true){ //       
                    testMessage(data){
                        if(data===true){  //     
                           
                        }else{  //      
                            
                        }
                    }    
                }
            }
        }
    }
}
コードには既に多くの問題があります.例えば、乱雑なif判断文、幾重にもネストされた関数は、コードの可読性の差をもたらし、維持が困難です.
また、度重なるコールバック関数に異常があったら、デバッグは非常に潰されます.try-catchは非同期の異常を捕まえられないので、私たちはデバッグを書いて追跡するしかないです.
このような幾重もの入れ子はリフロー地獄と呼ばれています.
1.2 Promise方式
Promiseは地獄問題を解決するために提出されました.これは新しい文法機能ではなく、新しい書き方です.コールバック関数のネストをチェーン式呼出しに変えられます.Promiseを採用し、複数のファイルを連続して読み込み、次のように書きます.
let state=1;  //      
function step1(resolve,reject){
    console.log('1.         ');
    if(state==1){
        resolve('   ');
    }else{
        reject('   ');
    }
}
function step2(resolve,reject){
    console.log('2.        ');
    if(state==1){
        resolve('    ');
    }else{
        reject('    ');
    }
}
function step3(resolve,reject){
    console.log('3.         ');
     if(state==1){
        resolve('     ');
    }else{
        reject('      ');
    }
}

new Promise(testRegister).then(function(val){ //         
    console.log(val);
    return new Promise(sendMessage);   //         
}).then(function(val){
     console.log(val);
    return new Promise(testMessage);  //          
}).then(function(val){
    console.log(val);
    return val;
});
コールバック関数は、ネスト方式で、testRegister()、sendMessage()とtestMessage()を順次呼び出し、Promiseはthenを使ってそれらをリンクします.
Promiseコードの読み取り可能性は、コールバック関数よりも高く、コードの実行順序は一目瞭然です.
Promiseの方式は地獄を転調しましたが、最大の問題はコード冗長で、元の任務はPromiseに包装されました.どんな操作でも、一目で見たらthenがいっぱいになり、元の意味がよく分かりません.コードフローは実行フローをよく表していません.
みんなは中学校で電気回路を習ったことがあります.これは電気回路の直列のようです.習ったことがないなら大丈夫です.jqueryにチェーン操作があると知っています.これはチェーン操作の書き方に似ています.
1.3 async/await方式
async文法はnew Promiseの包装に対してで、await文法はthen方法に対する洗練です.
 async function doRegister(url) {
  let data  = await testRegister();     //         
  let data2 = await sendMessage(data);  //         
  let data3 = await testMessage(data2); //          
  return data3
}
上のコードは短いですが、どれも重要です.dataはawait testRegisterの返却結果、data 2はsendMessageのパラメータとしてdataを使用し、data 3はtestMessageのパラメータとしてdata 2を使用した.
doregisterの前にキーワードasyncをつければ、関数内の非同期タスクの前にawait声明を追加すればいいです.これらの追加のキーワードを無視すれば、ほぼ完全な同期の書き方です.
2、asyncの使い方
2.1 Promiseオブジェクトに戻る
async関数はPromiseオブジェクトを返します.
async関数の内部return文で返した値は、thenメソッドのコールバック関数のパラメータになります.
async function f() {
  return 'aaa';
}

f().then(v => console.log(v))
//aaa
//Promise {: undefined}

2.2 await命令
通常、awaitコマンドの後にPromiseオブジェクトがあり、そのオブジェクトの結果に戻ります.Promiseオブジェクトでない場合は、直接に対応する値を返します.
/*    */
async function f() {
  return await 123;
}
f().then(value => console.log(value));  // 123
/*    */
async function f() {
  return Promise.reject('error');
}
f().catch(e => console.error(e));   // error
注意事項:
await命令はasync関数でしか使えません.普通の関数で使うと、エラーが発生します.
/*      */
function f(db) {
  let docs = [1, 2, 3];
  for(let doc of docs) {
    await db.push(doc);
  }
  return db; // Uncaught SyntaxError: Unexpected identifier
}





/*     (    ) */
async function f(db) {
  let docs = [1, 2, 3];
  for(let doc of docs) {
    await db.push(doc);
  }
  return db;
}

2.3 asyncにおける異常処理
async/awaitを使うことによって、私達はtry/catchと協力して非同期の作業中の問題を捕獲できます.Promiseの中でrejectのデータを含みます.
awaitの後ろにrejectがあるかもしれません.try...catchコードブロックの中で
async function f() {
  try {
    await Promise.reject('   ');
  } catch(e) {
    console.error(e);
  }
  return Promise.resolve('hello');
}
f().then(v => console.log(v));   //     hello
3、並列中のawait
async/await文法は確かに簡単で使いやすいですが、使い方も間違えやすいので、具体的なビジネスシーンの需要によって決めます.
例えば、画像のサイズ情報を取得したいです.
async function allPicInfo (imgs) {
  const result = [];
  for (const img of imgs) {
    result.push(await getSize(img));
  }
}
コードの中では、getSizeを呼び出すたびに、前回の呼び出しが完了するのを待つ必要があります.同じように性能が無駄で、しかも時間がかかります.同じ機能を使うと、このような方法がより適切です.
async function allPicInfo (imgs) {
  return Promise.all(imgs.map(img => getSize(img)));
}
複数の非同期動作が、継承関係がない場合は、同時にトリガすることが望ましい.
4、まとめ
最初のコールバック関数からPromiseオブジェクトに至るまで、毎回改善されていますが、中途半端な感じがします.それらはいずれも追加的な複雑さがあり、抽象的な底部の動作機構を理解する必要がある.
例えば、3つの要求が発生する必要があり、第3の要求は第2の要求の結果に依存し、第2の要求は第1の要求の結果に依存する.ES 5で実現すれば、3層のコールバックがあり、コードの横方向発展をもたらします.Promiseで実現するには少なくとも3つのthenが必要で、コードの縦方向の発展をもたらします.しかし、async/awaitはこれらの問題を解決しました.
実現してきたasync/awaitは、ジェネレータ、Promiseに基づいて構築された新しい文法です.
しかし、async/awaitを軽視しないでください.同期方式で非同期コードを書くのはとても強いです.
async/awaitは語義化、コードの簡略化、エラー処理などの面で多くの利点があります.async/waitで条件コードを作成するのは簡単で、同じコード構造(周知のtry/catch文)を使って同期と非同期エラーを処理することができます.だから、JavaScript異歩プログラミングの最終解決案と呼ばれています.その重要性と利点が分かります.
今後の実戦プロジェクトで、たくさん練習してこそ、async/awaitの真の技術を身につけることができると思います.