1. ホーム
  2. ジャバスクリプト

フロントエンド非同期(アシンク)ソリューション(全ソリューション)

2022-02-08 04:12:51
<パス

javascriptはシングルスレッド言語です。つまり、一度に完了できるタスクは1つだけで、実行するタスクが複数ある場合はキューに入れなければなりません(前のタスクが完了してから次のタスクが実行されます)。

このパターンは実行するのは簡単ですが、後に需要、トランザクション、リクエストが増加すると、このシングルスレッド・パターンは非効率になるに違いありません。あるタスクの実行に長い時間がかかる限り、その間に後のタスクを実行することはできないのです。よくあるブラウザの無反応(false death)は、Javascriptのコードの一部が長時間実行された結果(例えばデッドループ)、ページ全体がその場所で止まってしまい、他のタスクが実行できなくなることがよくあります。( 短所 )

この問題を解決するために、javascript言語では、タスクの実行モードを同期と非同期に分離しています。

同期モード : 前述の実行モードの一つで、後者のタスクは前のタスクの終了を待ってから実行され、プログラムの実行順序はタスクのリスト順と一致し同期している。

非同期モード : これは、各タスクが1つ以上のコールバックを持つことを意味します。前のタスクが終了すると、後のタスクを実行する代わりにコールバックを実行し、前のタスクの終了を待たずに後のタスクを実行するので、プログラムの実行順序はタスクの順序と矛盾し非同期となる。

非同期モードは非常に重要です。ブラウザ側では、時間がかかる操作は非同期で実行して、ブラウザが応答しなくなるのを防ぐ必要があります。その最たるものがAjax操作です。サーバー側では、非同期モードは唯一のモードとさえ言えます。なぜなら、実行環境はシングルスレッドであり、もしすべてのhttpリクエストを同期で実行することを許可したら、サーバーのパフォーマンスは劇的に低下し、あっという間にレスポンスが失われることになるからです。( 非同期モードの重要性 )

フロントエンドの非同期ソリューションをいくつか紹介します。

I. 従来の解決策

1. コールバック関数(callback)。

非同期プログラミングの基本的な考え方。

まず最初に、コールバック関数は一つの実装に過ぎず、非同期モデルに特化したものではないことを明記しておく。コールバック関数は、同期(ブロッキング)シナリオだけでなく、他のいくつかのシナリオでも使用することができます。

コールバック関数の定義

関数Aが別の関数Bにパラメータ(関数参照)として渡され、この関数Bが関数Aを実行することをコールバック関数と呼ぶ。名前(関数式)がない場合は、アノニマスコールバック関数と呼ぶ。

生活の例。デートの後、恋人を家まで送り、別れ際に「心配だから、帰ったらメッセージ送ってね」と言う。そして、実際に恋人が帰宅したら、あなたにメッセージを送る。これは実はコールバック処理なんです。パラメータ関数(恋人にメッセージを送ってもらう)を恋人に残し、恋人が帰宅する、その帰宅する動作がメイン関数となります。彼女はmain関数の実行が終わったらまず帰宅し、次に渡された関数を実行し、そしてメッセージを受け取らなければなりません。

事例を紹介します。

// Define the main function, with the callback function as an argument
function A(callback) {
    callback();  
    console.log('I am the main function');      
}
 
// Define the callback function
function B(){
    setTimeout("console.log('I am the callback function')", 3000);//imitates a time-consuming operation  
}
 
// call the main function and pass function B in
A(B);
 
//output the result
I am the main function
I am the callback function


上のコードでは、まずmain関数とコールバック関数を定義し、次にコールバック関数を渡してmain関数を呼び出しています。

main関数を定義する際に、callback()コールバック関数を先に実行させるのですが、出力結果は後からコールバック関数が出力されるようになっています。つまり、main関数はコールバック関数の実行が終了するのを待つ必要がなく、その後に自身のコードを実行することができます。そのため、一般的にコールバック関数は時間のかかる処理で使用されます。例えば、ajaxリクエストやファイル操作などだ。

長所 シンプルでわかりやすく、導入しやすい。

デメリット: コードリーディングやメンテナンス性が悪い、パーツ間の結合が強く、フローがわかりにくい、1タスクに1コールバック関数しか指定できない、などです。

2. イベントリスニング

イベントドリブンモードを使用します。

タスクの実行はコードの順番ではなく、特定のイベントが発生するかどうかに依存する。

リスナー関数は、on、bind、listen、addEventListener、observeです。

f1とf2を例にとって説明しよう。まず、f1(jqueryで記述)にイベントをバインドする。

f1.on('done',f2);


上記のコードは、f1がdoneイベントを発生させると、f2が実行されることを意味します。

次にf1を書き換えます。

function f1(){
    settimeout(function(){
   // Task code for f1
   f1.trigger('done');  
},1000);
}


f1.trigger('done') は、実行が完了すると直ちに done イベントが発生し、f2 の実行が開始されることを意味します。

長所 理解しやすい、複数のイベントをバインドできる、各イベントに複数のコールバック関数を指定できる、モジュール化を促進するためにデカップリングできる、など。

デメリット プログラム全体がイベントドリブンにならざるを得ず、実行時の流れが不明瞭になる。

イベントの識別方法。

(1).onclickメソッド。

element.onclick=function(){
   //handle function
}


プロフェッショナル : 主要なブラウザに対応するように書かれています。

デメリット : 複数のイベントが同じ要素エレメントにバインドされている場合、最後のイベントのみが追加されます。

element.onclick=handler1;
element.onclick=handler2;
element.onclick=handler3;


アピールに追加されるのはhandler3のみなので、別のメソッドでイベントを追加する。(2) attachEventメソッドとaddEvenListenerメソッド

(2). attachEventとaddEvenListenerのメソッド。

//IE:attachEvent (event listener under IE)
elment.attachEvent("onclick",handler1);
elment.attachEvent("onclick",handler2);
elment.attachEvent("onclick",handler3);


上記3つの方法の実行順です。3-2-1.

// standard addEventListener (listener under standard)
elment.addEvenListener("click",handler1,false);
elment.addEvenListener("click",handler2,false);
elment.addEvenListener("click",handler3,false);>


実行の順番 1-2-3.

PS: このメソッドの第3パラメータはバブルキャプチャ(useCapture)で、これはブール値です。偽の場合はインサイドアウト(イベントバブリング)、真の場合はアウトサイドイン(イベントキャプチャ)を意味します。

document.getElementById("id1").addEventListener("click",function(){ console.log('id1'); },false); document.getElementById("id2").addEventListener("click",function({ console.log('id2'); },false); //click on the div with id=id2, output in console first, output id2 first, output id1 in document.getElementById("id1").addEventListener("click",function({ console.log('id1'); },false); document.getElementById("id2").addEventListener("click",function({ console.log('id2'); },true); //click on the div with id=id2, output in console, first id1, then id2
on:function(elment,type,handler){
   //add event
   return element.attachEvent? elment.attachEvent("on"+type,handler):elment.addEventListener(type,handler,false);
}


(3).DOM メソッド addEventListener()と removeListenner()。

addEventListenner() および removeListenner() は、イベントの割り当てと削除に使用される関数を表します。どちらのメソッドも 3 つの引数を取ります。文字列 (イベント名)、イベントをトリガーする関数、およびイベントのハンドラー関数を指定する period または phase (ブール値) です。 例として(2)をご覧ください。

(4). 一般的な時間足し算の方法。

new Promise(
    /* executor */
    function(resolve, reject) {
        if (/* success */) {
            // ... Execute the code
            resolve();
        } else { /* fail */
            // ... Execute the code
            reject();
        }
    }
);


イベントバブリングとイベントキャッチの違いについては、こちらをご覧ください。

II. ツールオプション

ツールプログラムは、大きく分けて以下の5つです。

  • プロミス
  • ジェネレータ関数
  • 非同期await
  • nextTick setImmidate in node.js
  • サードパーティライブラリ async.js

それぞれについて、以下に詳しく説明します。

1. プロミス(フォーカス)

(1).プロミスの意味と展開。

意味 Promiseオブジェクトは、非同期処理の最終的な完了(または失敗)とその結果値を表現するものです。簡単に言うと、非同期処理に成功した場合は処理を実行し、非同期処理に失敗した場合はエラーをキャッチしたり、後続の処理を停止したりするために使用されます。

開発:Promiseは、従来のソリューションであるコールバック関数やイベントよりも理にかなっており、より強力な非同期プログラミングのためのソリューションです。コミュニティによって最初に提案され、実装されました。ES6では言語標準に組み込まれ、構文が統一され、Promiseがネイティブで提供されるようになりました。

(2). その一般形.

var promise1 = new Promise(function(resolve, reject) {
  // Set to receive state after 2 seconds
  setTimeout(function() {
    resolve('success');
  }, 2000);
});

promise1.then(function(data) {
  console.log(data); // success
}, function(err) {
  console.log(err); // not executed
}).then(function(data) {
  // The then() method in the previous step does not return a value
  console.log('chain call: ' + data); // chain call: undefined 
}).then(function(data) {
  // ....
});


ここで、Promiseの引数executorは、2つの引数を持つexecutor関数です。 解決する 拒否する . 通常、内部にいくつかの非同期操作を持ち、非同期操作が成功すれば、 resolve() を呼び出してインスタンスの状態を 履行済 に設定し、失敗した場合は reject() を呼んでインスタンスの状態を 却下 これは失敗を意味します。

Promiseオブジェクトは、工場の組み立てラインと考えることができます。組立ラインには、その仕事の機能から見て、初期状態(最初に起動したとき)、成功した製品、失敗した製品(何らかの失敗)の3つの状態しかありません。同様にPromiseオブジェクトの場合も、3つの状態を持ちます。 保留中です。 初期状態は保留状態とも呼ばれ、Promiseの初期化時にエグゼキュータ関数が呼ばれた後の状態です。 fulfilled: 非同期処理が成功したことを意味するフルフィルド状態。

  • 保留中です。 初期状態とは、保留状態とも呼ばれ、Promiseの初期化時にエグゼキューター関数が呼ばれた後の状態のことです。
  • を満たした。 非同期操作が成功したことを意味する完了ステータス。
  • 拒否されました。 非同期操作に失敗したことを意味する失敗ステータス。

変換できる状態は次の2つだけです。

  • 操作に成功した。 保留 -> 充足
  • 操作に失敗しました。 保留 -> 拒否

また、この状態遷移は一方通行で不可逆であり、決定した状態(fulfilled/rejected)は初期状態(pending)に戻すことができない。

(Promiseオブジェクトのメソッド(api):)

1):Promise.prototype.then(コールバック)

Promiseオブジェクトにはthenメソッドがあり、then()呼び出しはPromiseオブジェクトを返します。つまり、インスタンス化されたPromiseオブジェクトはチェーンで呼び出すことができ、このthen()メソッドは成功後の処理関数とエラー結果を処理する関数を2つ取ることができるのです。

以下の通りです。

var promise3 = new Promise(function(resolve, reject) {
  setTimeout(function() {
    reject('reject');
  }, 2000);
});

promise3.then(function(data) {
  console.log('Here is the fulfilled state'); // It won't trigger here
  // ...
}).catch(function(err) {
  // The last catch() method catches the exceptions in this Promise chain
  console.log('Error: ' + err); // Error: reject
});


ここでは主に promise1.then() メソッド呼び出し後に返された Promise オブジェクトの状態、保留中か、full-filled か、拒否されたか、に着目しています。

このPromiseオブジェクトが返される状態は、promise1.then()メソッドが返す値によって大きく左右され、大きく以下のケースに分かれます。

1. then()メソッドでパラメータ値が返された場合、返されたPromiseが受信状態となる。

2. then()メソッドで例外が発生した場合、返されるPromiseはrejectの状態になります。

3. then() メソッドが resolve() メソッドを呼び出すと、返された Promise は received となる。

4. then()メソッドがreject()メソッドを呼び出した場合、返されるPromiseはrejectの状態になります。

5. then()メソッドで返された新しいPromiseのインスタンスが未知の状態(pending)であった場合、そのPromiseは未知の状態になります。

6. then() メソッドで resolve(data)/reject(data)/return data を明示的に指定しない場合、返される新しい Promise は受信状態であり、1層ずつ下に渡すことができる。

2):Promise.prototype.catch(コールバック)

catch() メソッドは then() メソッドと同様に新しい Promise オブジェクトを返し、主に非同期処理中に発生した例外をキャッチするために使用されます。そのため、通常は以下のようにthen()メソッドの第2引数を省略し、エラー処理の制御をその後に続くcatch()関数に渡します。

const p1 = new Promise((resolve,reject)=>{
  setTimeout(()=>{
    resolve(console.log('p1 Task 1'))
  },1000)
})
  .then( data => {
    console.log('p1 Task 2')
  })
  .then( res => {
    console.log('p1 Task 3')
  })
  .catch( err => { throw err} )

const p2 = new Promise((resolve,reject)=>{
  resolve(console.log('p2 Task 1'))
}).then(
  data => {
    console.log('p2 Task 2')
  }
).catch(
  err => {
    throw err 
  }
)
// the content of then will be executed only after p1,p2 are executed
Promise.all([p1,p2])
 .then(()=>console.log('done'))



3):Promise.all()の場合

Promise.all()は引数を取りますが、引数は配列のように反復可能である必要があります。

これは通常、結果が互いに干渉しないが、非同期に実行される必要がある同時並行処理を扱うために使用されます。最終的に成功か失敗かの2つの状態しか持たない。

は、配列内のすべてのタスクが、.thenのタスクが実行される前に実行されることを意味します。

その状態は、引数の各値の状態に影響される。つまり、中の状態がすべて満たされたときに満たされ、そうでないときは拒絶されることになる。

呼び出しに成功すると、値が順序付けられた配列、つまり引数に渡された値に従って配列を操作した結果が返されます。

以下のように。

var p1 = new Promise(function(resolve, reject) {
  setTimeout(resolve, 300, 'p1 doned');
});

var p2 = new Promise(function(resolve, reject) {
  setTimeout(resolve, 50, 'p2 doned');
});

var p3 = new Promise(function(resolve, reject) {
  setTimeout(reject, 100, 'p3 rejected');
});

Promise.race([p1, p2, p3]).then(function(data) {
  // Obviously p2 is faster, so the state becomes fulfilled
  // If p3 is faster, then the state becomes rejected
  console.log(data); // p2 doned
}).catch(function(err) {
  console.log(err); // not executed
});


4):Promise.race()(プロミス)

Promise.race()はPromise.all()と同様に引数を取って反復処理しますが、Promise.race()の状態変化は引数内の状態の影響をすべて受けないという違いがあります。 引数内の値の状態が変化すると、Promiseの状態は変化した状態になります。ちょうど、レースという言葉が文字通り、速く走った方が勝ち!という意味であるように。 . 次のようになります。

// The arguments are ordinary values
var p4 = Promise.resolve(5);
p4.then(function(data) {
  console.log(data); // 5
});

// The argument is the object containing the then() method
var obj = {
  then: function() {
    console.log('then() method inside obj');
  }
};

var p5 = Promise.resolve(obj);
p5.then(function(data) {
  // The value here is the value returned inside the obj method
  console.log(data); // the then() method inside obj
});

// The argument is a Promise instance
var p6 = Promise.resolve(7);
var p7 = Promise.resolve(p6);

p7.then(function(data) {
  // The value here is the value returned by the Promise instance
  console.log(data); // 7
});

// The argument is the Promise instance, but the argument is the rejected state
var p8 = Promise.reject(8);
var p9 = Promise.resolve(p8);

p9.then(function(data) {
  // The value here is the value returned by the Promise instance
  console.log('fulfilled:' + data); // not executed
}).catch(function(err) {
  console.log('rejected:' + err); // rejected: 8
});


5):Promise.resolve()

Promise.resolve()は、引数として通常の値、then()メソッドを持つオブジェクト、およびPromiseインスタンスを受け取ります。通常は状態が満たされたPromiseオブジェクトを返しますが、解決中にエラーが発生した場合は、返されたPromiseオブジェクトは拒否された状態に設定されます。以下のようになります。

var p10 = Promise.reject('manually reject');
p10.then(function(data) {
  console.log(data); // it won't execute here because it's the rejected state
}).catch(function(err) {
  console.log(err); // manually rejected
}).then(function(data) {
 // not affected by the previous level
  console.log('status: fulfilled'); // status: fulfilled
});


6):Promise.reject()

Promise.reject()は、Promise.resolve()の逆で、引数値としてreasonを1つ取るというものです。返されたPromiseオブジェクトはrejectedの状態に設定されます。以下のようになります。

function* showWords() {
    yield 'one';
    yield 'two';
    return 'three';
}

var show = showWords();

show.next() // {done: false, value: "one"}
show.next() // {done: false, value: "two"}
show.next() // {done: true, value: "three"}
show.next() // {value: underfined, done: true}


まとめると、Promise.then()メソッドが内部で例外を投げるか、明示的にリジェクト状態にならない限り、それが返すPromiseの状態は満たされる、すなわち完了し、その状態は親の状態の影響を受けないということです。

2. ジェネレータ機能

非同期プログラミングにおけるもう一つの一般的な解決策は、Generatorジェネレータ関数です。その名の通り、これはジェネレータであり、値や関連する状態を内部に保持するステートマシンでもある。ジェネレーターはイテレータIteratorオブジェクトを返し、それを通して関連する値、状態を手動で反復し、正しい実行順序を確保することができる。

es6 が提供するジェネレータ機能

一言で言えば、3つです。

* 関数キーワードに * を付けると、その関数はジェネレータ関数と呼ばれます。

* 関数本体には、各タスクに続いてyieldキーワードがあり、データを保持するためにreturnキーワードを持つことができます。

* 次の関数から呼び出され、何度か呼び出され、何度かタスクが実行されます。

(1). 使い方が簡単

Generator は通常の関数宣言と同じように宣言されますが、* 記号が追加され、関数内部では通常 yield キーワードを目にすることができます。

function* g1(){
  yield 'task1'
  yield 'task2'
  yield 'task3'
  return 'task4'
}

const g1done = g1()

console.log(g1done.next()) //{ value: 'Task 1', done: false }
console.log(g1done.next()) //{ value: 'Task 2', done: false }


上記のコードでは、showWordsのジェネレータ関数が定義されており、これが呼び出されてイテレータオブジェクト(つまりshow)を返します。

次のメソッドを呼び出した後、最初の yield 文が関数内で実行され、done された現在の状態(イテレータが走査されたかどうか)と対応する値(通常は yield キーワードに続く操作の結果)が出力されます。

nextが呼ばれるたびにyield文が実行され、そこで一時停止し、returnが完了するとジェネレータ関数が終了し、それ以降にyield操作があったとしても実行されなくなる

そしてもちろん、次のようなケースもあります: (next() has less than yield)

function* showWords() {
    yield 'one';
    yield showNumbers();
    return 'three';
}

function* showNumbers() {
    yield 10 + 1;
    yield 12;
}

var show = showWords();
show.next() // {done: false, value: "one"}
show.next() // {done: false, value: showNumbers}
show.next() // {done: true, value: "three"}
show.next() // {done: true, value: undefined}


(2).イールドとイールド*。

yieldの後に*記号が付くことがありますが、これは何でしょうか、またどのような働きをするのでしょうか。

ジェネレーターの前の*印と同様に、イールドの後のアスタリスクも、大きな栗のようにジェネレーターに関係しています。


function* showWords() {
    yield 'one';
    yield* showNumbers();
    return 'three';
}

function* showNumbers() {
    yield 10 + 1;
    yield 12;
}

var show = showWords();
show.next() // {done: false, value: "one"}
show.next() // {done: false, value: 11}
show.next() // {done: false, value: 12}
show.next() // {done: true, value: "three"}


showWordsで一度呼び出したいジェネレータ関数を追加した後、単純なyield showNumbers()では関数内のyield 10+1が実行されないことがわかります。

yieldは右辺をそのまま返すだけなので、しかし今のshowNumbers()は通常の関数呼び出しではなく、イテレータオブジェクトを返しています

そこで、yield*を変更して、自動的にそのオブジェクトに反復処理を行うようにします。

function showWords() {
    yield 'one'; // Uncaught SyntaxError: Unexpected string
}


yield と yield* はジェネレータ関数の中でのみ使用可能であり、通常の関数内で使用するとエラーになることに注意してください。

function showWords() {
    yield* 'one'; 
}

var show = showWords();

show.next() // Uncaught ReferenceError: yield is not defined


yield* に変更しても直接エラーは報告されませんが、'one' 文字列には Iterator インターフェースがなく、トラバーサルを提供する yield もないため、これを使用すると問題が発生します。

var urls = ['url1', 'url2', 'url3'];

function* request(urls) {
    urls.forEach(function(url) {
        yield req(url);
    });

// for (var i = 0, j = urls.length; i < j; ++i) {
// yield req(urls[i]);
// }
}

var r = request(urls);
r.next();

function req(url) {
    var p = new Promise(function(resolve, reject) {
        $.get(url, function(rs) {
            resolve(rs);
        });
    });

    p.then(function() {
        r.next();
    }).catch(function() {

    });
}


クローラー開発では、複数のアドレスをリクエストする必要があることが多く、順番を確保するために、PromiseオブジェクトとGeneratorジェネレータ関数を導入していますが、このシンプルなクリをご覧ください。

function* showNumbers() {
    var one = yield 1;
    var two = yield 2 * one;
    yield 3 * two;
}

var show = showNumbers();

show.next().value // 1
show.next().value // NaN
show.next(2).value // 6


上記の forEach のコードは url 配列を繰り返し、yield キーワードは無名関数内部では使用できないため、コメント内の for ループに置き換えてください。

(3).next()の呼び出しでパラメータを渡す

パラメータ値は、次のように前の yield の戻り値を変更するために注入する機能があります。

var urls = ['url1', 'url2', 'url3'];

function* request(urls) {
    var data;

    for (var i = 0, j = urls.length; i &lt; j; ++i) {
        data = yield req(urls[i], data);
    }
}

var r = request(urls);
r.next();

function log(url, data, cb) {
    setTimeout(function() {
        cb(url);
    }, 1000);
    
}

function req(url, data) {
    var p = new Promise(function(resolve, reject) {
        log(url, data, function(rs) {
            if (!rs) {
                reject();
            } else {
                resolve(rs);
            }
        });
    });

    p.then(function(data) {
        console.log(data);
        r.next(data);
    }).catch(function() {
        
    });
}


ジェネレータが自動的に対応する変数の値を保存しないため、次への最初の呼び出しの後、戻り値の1つは1ですが、次への2番目の呼び出しで、実際に未定義であり、我々は手動で指定する必要があり、その後2値は、次への3番目の呼び出しで、最後の降伏戻り値の2への参照を渡すことによって、3 * 2をもたらす実装では、結果を取得するNaNであり

もう一つの栗。

ajaxリクエストはネットワークを巻き込み、うまく処理できないので、ここではsetTimeoutを使って、ajaxリクエストを順番に返すシミュレーションを行い、その都度返されたデータを渡しています

function* showNumbers() {
    yield 1;
    yield 2;
    return 3;
}

var show = showNumbers();

for (var n of show) {
    console.log(n) // 1 2
}


最初は引数なしで直接 r.next() し、その後 r.next(data) でデータデータを渡すことで、3つのアドレスを順番に要求する効果を実現します。

コードの16行目に注目してください。ここでは、url変数がデータデータと比較するためのパラメータとして使用されています。

最初のnext()には引数がないので、urlを直接dataに置き換えると、!rs == undefinedと判断して、promiseオブジェクトのdataを拒否します

そこで、16行目をcb(data || url)に置き換えます。

シミュレートされたajaxの出力で、あなたは次の渡された値を理解することができ、ログ出力の最初の時間は、URL = 'url1'値であり、その後のデータ= 'url1'のreqリクエストに渡す、ログ出力でデータ= 'url1' 値です。

(4) .next()の代わりに .for... .of ループを使用する。

.next()メソッドによるイテレータオブジェクトの反復処理に加えて、ES6が提供する新しいループ for...of も反復処理が可能ですが、nextとは異なり、returnで返される値を無視するため、次のようになります。

function* showNumbers() {
    yield 1;
    yield 2;
    return 3;
}

var show = showNumbers();

[... .show] // [1, 2, length: 2]


また、イテレータインターフェースを呼び出すメソッドによるループのfor...の処理も、拡張演算子...の使用など、生成関数をトラバースできる。

async function aa(){
        await 'Task 1'
        await 'Task 2'
}


その他の使い方は、: MDN - ジェネレータ

3. 非同期await (フォーカス)

es7 の新しい非同期関数

は、promise を使ってより快適に動作させることができます。

(1). フォーマット

async function timeout() {
  return 'hello world';
}


非同期

では、まず asyncキーワード これは関数の前に置かれます。次のような感じです。

async function f() {
    return 1
}
f().then(alert) // pop up 1



関数の前にある async という単語が意味するのは簡単なことで、その関数は常にプロミスを返し、コードに return <non-promise> という記述があれば、JavaScript は自動的にその戻り値をプロミスの解決値で包むのです。

例えば、上記のコードがプロミスの解決値1を返した場合、テストすることができます。

async function f() {
    return Promise.resolve(1)
}
f().then(alert) // pop up 1


また、明示的にプロミスを返すことも可能で、その場合も同じ結果になります。

// can only be used inside async functions
let value = await promise


つまり、asyncは関数がプロミスでないものを含んでいても、確実にプロミスを返すので、複雑なプロミスを書く必要がないのですね。しかし、それだけではありません。もう一つキーワードがあります アウェイト は、非同期関数でのみ使用でき、また、かなりクールです。

await

async function f() {
    let promise = new Promise((resolve, reject) => {
        setTimeout(() => resolve('done!'), 1000)
    })
    let result = await promise // until the promise returns a resolve value (*)
    alert(result) // 'done! 
}
f()


awaitというキーワードを使うと、JavaScriptはプロミスが実行されてその結果が返ってくるまで待機することができます。

以下は、1秒後に解決するプロミスの例です。

function f() {
   let promise = Promise.resolve(1)
   let result = await promise // syntax error
}
//Uncaught SyntaxError: await is only valid in async function


関数は (await) 行で '一時停止' して、それ以上進めません。 プロミスの処理が終わって再開すると、resolveの値が最終結果となるので、上記のコードでは1秒後に「done!」と出力されます。

ここで強調しておきたいのは、awaitは文字通り、プロミスの処理が終わるまでJavaScriptを待たせるものであり

を実行し、その結果を継続します。エンジンは同時に他のこと(他のスクリプトの実行、イベントの処理など)を行うことができるため、CPUリソースを消費することはないのです。

これは、promiseの値を取得するためのよりエレガントな文に過ぎず、promiseよりも読み書きが容易である。

注:通常の関数でawaitを使用することができます。

非同期関数でawaitを使おうとすると、次のような構文エラーが発生します。

async function testResult() {
    let first = await doubleAfter2seconds(30);
    let second = await doubleAfter2seconds(50);
    let third = await doubleAfter2seconds(30);
    console.log(first + second + third);
}


関数の前にasyncを置き忘れると、このようなエラーになります。前述したように、awaitはasync関数の中でしか動作させることができません。

最初のいくつかのケースだけでは、async/awaitが何をするのか明らかではないかもしれません。3つの数値の値を計算し、その結果の値を出力したい場合はどうでしょうか。

const fs = require('fs')//import the fs module

const readFile = (filename) =>{
  return new Promise((resolve,reject)=>{
    fs.readFile(filename,(err,data)=>{
      resolve(data.toString())
    })
  })
}

const asyncFn = async() => {
   //const f0 = eadFile('. /01-Promise.js') // something like {value: 'file content', done: false}
  const f1 = await readFile('. /01-Promise.js') //file content
  //const f1 = readFile('. /01-Promise.js').then(data=>data)

  const f2 = await readFile('. /02-generator.js') //file content
  console.log( f1 )
  console.log( f2 )
}
asyncFn()


6秒後、コンソールに220と出力され、非同期コードを書くことが、コールバックのロケールがなくなり、同期コードを書くようになったことが分かります。

もう一度見てみよう:まず、質問です。

readFile('. /01-Promise.js') は Promise として実行されますが、async await を使用すると具象データになるのですね。

Node.jsのfsモジュールはファイルを操作するモジュールで、readFile()はファイルを読み込むモジュールです。

async.parallel(&#91;
function(callback){
callback(null,'task1')
},
function(callback){
callback(null,'Task 2')
},
],(err,data)=>{
console.log('data',data)
})


readFile()は、ピットがあるファイルを読み取るためにPromiseメソッドを定義して、我々は今、データのうち、関数の3つの層があることを知って返す内側には、新しいPromiseメソッドを使用しない場合は、通常のメソッドを使用してデータを返すことはできませんしようとすると、最初の底を取るを介して、あなたが試すことができます。

asyncFn()は、ファイルの内容を const f1 = eadFile('. /01-Promise.js') この文章は、Promise{'file content'}を出力します。前回のジェネレータ関数 output {value: '', done: false}とやや似ていますが、doneだけが省略されており、我々は知っていて、ファイルを読み、確かに中の内容を知りたい、もし、Promise{'file content'} を出力したら、よくない。 awaitは前にawaitをつけてファイルを直接出力すればいいのだから、この問題の解決になります。

つまり、問題の概要は以下の通りです。

1. 非同期関数は、オブジェクト {value: '',done:false} を直接生成するジェネレータ関数の構文解析を使用して、値を直接抽出するように待ちます。

2. Promise + async で、ネストされた(非同期に実行された)多層関数の内部関数のデータを返すことができる

async/awaitの概要

関数の前に配置する 非同期 には2つの役割があります。

  • この関数は常にプロミスを返すようにする
  • この中でawaitを使用できるようにする

プロミスの前に 待つ キーワードは、JavaScript がプロミス処理が終了するまで待機することを可能にします。次に

  • エラーであれば、その場所でthrow errorが呼ばれた場合と同様に、例外がスローされます。
  • そうでなければ、結果を返し、それを値に代入することができます。

これらは共に、読み書きが容易な非同期コードを書くための素晴らしいフレームワークを提供します。

async/awaitを使えば、promise.then/catchを書く必要はほとんどありませんが、それでもこれらのメソッドを使わなければならないとき(一番外側のスコープなど)があるので、promiseをベースにしていることは忘れてはいけません。promise.allは、一度に多くのタスクを待機できることも大きな魅力ですね。

4. node.js nextTick setImmidate

nextTick と setImmediate の比較

ポーリング

がイベントドリブンである場合、イベントキューからタスクをフェッチし続けるスレッドが定期的に存在します。
I/O操作はバックグラウンドスレッドのプールに渡され、このループの各実行がポーリングセッションとみなされる。
2. setImmediate()の使用
即時タイマーはすぐに仕事を実行します。イベントがポーリングされた後に実行され、ポーリングがブロックされるのを防ぐために、一度に1つだけ呼び出されます。
3. Process.nextTick()の使用
setImmediate() と同じ順序で実行されるのではなく、イベントポーリングの前に実行され、I/O飢餓を防ぐため、デフォルトで process.maxTickDepth=1000 とし、イベントキューのループごとに実行できる nextTick() イベントの数を制限しています。

まとめると

nextTick()コールバック関数は、setImmediate()よりも高い優先順位で実行されます。
process.nextTick()はアイドルオブザーバーで、setImmediate()はチェックオブザーバーです。ループチェックの各ラウンドでは、アイドルオブザーバはI/Oオブザーバに先行し、I/Oオブザーバはチェックオブザーバに先行する。
実装面では、process.nextTick()のコールバック関数が配列に格納される。
setImmediate()の結果は、チェーンに格納されます。
動作としては、process.nextTick()は、ループの各ラウンドで配列内のすべてのコールバック関数を実行します。
一方、setImmediate() はループごとにチェーンから1つのコールバックを実行します。

5. サードパーティライブラリ async.js

async.js は、多くのAPIを持つサードパーティライブラリです。

並列、直列など多くのAPIを持つ非同期オブジェクトを公開する(マルチタスク)。

async.parallel(&#91;
function(callback){
callback(null,'task1')
},
function(callback){
callback(null,'Task 2')
},
],(err,data)=>{
console.log('data',data)
})