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

フロントエンド非同期ソリューションブック(2021年版)

2022-02-27 05:02:11

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

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

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

同期モード。 上記の実行モードの一つで、後者のタスクが前のタスクの終了を待って実行するもので、プログラムの実行順序はタスクのリスト順と一致し、同期しているのが特徴である。

非同期モード。 各タスクは1つ以上のコールバック関数(callback)を持つ 後者のタスクは前のタスクの終了を待たずに実行されるため、プログラムの実行順序はタスクのリスト順と矛盾し、非同期となる。

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

タスクの実行順序の詳細です。 ❤️ jsのマクロタスクとマイクロタスクについてみんなで語ろう! _紙飛行機ブログ - CSDNブログ

小さなお子様向けにはこんなのもあります。 <スパン フロントエンドの非同期ソリューション。

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 result
//I'm the main function
//I am the callback function

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

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

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

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

2. イベントリスニング

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

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

リスニング関数とは、on、bind、listen、addEventListener、observeなどです。

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

f1.on('done',f2);
//The above code means that when f1 fires a done event, f2 is executed.

そして、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のみなので、別のメソッド(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.

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

<div id="id1">
    <div id="id2"></div>
</div>
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, then id1

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, first 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);
}


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

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. プロミス(フォーカス)

プロミスの意味と展開

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

開発です。 Promiseは、従来のソリューション(コールバック関数やイベントリスナー)よりも優れた非同期プログラミングのためのソリューションです <スパン より感覚的に、よりパワフルに . というコミュニティから提案され、実行されたのが最初です。 ES6 はそれを言語標準に組み込み、構文を統一し、Promiseをネイティブに提供した。

一般的な形では

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引数はresolveとrejectの2つの引数を持つexecutor関数です。通常はその中にいくつかの非同期処理を持ち、非同期処理が成功すればresolve()を呼んでインスタンスの状態をfulfilled、つまり完了とし、失敗すればreject()で状態をfailedに設定できます。

Promiseオブジェクトは、ファクトリーのパイプラインと考えることができます。パイプラインの場合、そのジョブ機能からすると、初期状態(最初に起動したとき)、成功した処理品、失敗した処理品(何らかの障害が発生した)の3つの状態しかありません。Promiseオブジェクトも同様に、次の3つの状態を持ちます:pending:初期状態、未定義状態とも呼ばれ、Promiseの初期化、executor実行関数を呼び出した後の状態です。 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オブジェクトの状態、つまり、promise1.then()メソッド呼び出し後の 保留中か、満杯か、拒否されたか? 

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

  1. then()メソッドで引数値を返すと、返されたPromiseが受信状態になる。
  2. then() メソッドで例外が発生した場合、返された Promise は拒否されます。
  3.  then() メソッドが resolve() メソッドを呼び出した場合、返されるPromiseは受信状態になります。
  4. then()メソッドがreject()メソッドを呼び出した場合、返されるPromiseはrejectの状態になります。
  5. then()メソッドが未知の状態(保留)のPromiseの新しいインスタンスを返した場合、返された新しい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つあります。

  1. * 関数キーワードに * を付けると、その関数はジェネレータ関数と呼ばれます。
  2. * 関数本体には、各タスクに続いてyieldキーワードがあり、データを保持するためにreturnキーワードを持つことができます。
  3. * 次の関数で呼び出され、数回の呼び出し、数人のタスク実行

シンプルな使い方

ジェネレータは、通常の関数宣言と同様の方法で宣言されますが、その際に追加で * 記号があり、通常、関数内部で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}


yieldとyield*を追加

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 &lt; 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ループに置き換えるだけです。

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番目の呼び出しで1つは実際に未定義で、ジェネレータが自動的に対応する変数の値を保存しないため、我々は手動で指定する必要があり、その後2値は、次への3番目の呼び出しで3 * 2、最後への参照を渡すことによって収量する実装です。 結果は2への最後の戻り値を渡すことによって取得されます。

もう一つの栗。

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に置き換えると、promiseオブジェクトのdata判定が !rs == undefinedなので、リジェクトされます。

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

ajax出力のシミュレーションでは、nextの渡された値が表示されています。ログに最初に出力されるのは url = 'url1' の値で、その後の req リクエストに data = 'url1' を渡すと、ログに data = 'url1' という値が出力されます。

for... .next()の代わりに.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'
}

その他の用途は、.NETでご覧いただけます。 MDN - ジェネレータ .

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

エスセブン 新しい非同期関数が追加されました。

は、promiseをより快適に動作させることができる、async/awaitと呼ばれるもので、非常に理解しやすく使いやすいです。

書式と知っておくべきこと

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()はファイルを読み込むモジュールです。

// Unlike series/waterfall, tasks are executed side-by-side and callback is not executing the next task. 
async.parallel([
    function(callback){
        callback(null,'task1')
    },
    function(callback){
        callback(null,'task2')
    },
],(err,data)=>{
    console.log('data',data)
})

//execute the tasks in the array in order, no callback will execute the next task
async.series([
    function(callback){
        // do some stuff ...
        callback(null, 'one');
    },
    function(callback){
        // do some more stuff ...
        callback(null, 'two');
    }
],
// optional callback
function(err, results){
    // results is now equal to ['one', 'two']
});


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

asyncFn()はファイルの内容を出力します、const f1 = eadFile('. /01-Promise. js') この文は、Promise{'ファイルの内容'}を出力されます、やや前のジェネレータ関数出力{値: ''、done: false}に似て、だけ行わ省略、あなたが知っているように、我々はファイルを読んで、それは内部のコンテンツでなければなりません、出力Promise{'ファイルの内容'}場合、我々はコンテンツを取り出すのは得意ではありませんが、待っては良いソリューションですこの問題は、フロントプラス待って直接ファイルの内容を出力解決に役立つと思います。

そこで、問題の概要を少し説明します。

<ブロッククオート
  1. 非同期関数は、オブジェクト {value: '',done:false} を直接生成するジェネレータ関数の構文シュガーを使って、値を直接抽出するように待ちます。
  2. Promise + asyncで、ネストされた(非同期に実行される)多層関数のインナー関数のデータを返すことができる

async/awaitの概要 

関数の前に置かれたasyncは、2つの目的を果たす。

関数が常にプロミスを返すようにすることで、await をこのように使うことができるようになります。
promiseの前にawaitキーワードをつけると、JavaScriptはpromiseの処理が終わるまで待つことができるようになります。では

エラーであれば、その場所でthrow errorが呼ばれたのと同じように例外がスローされます。
そうでなければ、結果を返し、それを値に代入することができます。
これらは共に、読み書きが容易な非同期コードを書くための素晴らしいフレームワークを提供します。

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

4. node.js nextTick setImmidate

nextTick と setImmediate の比較

(1). ポーリングとは

イベントドリブンであるnodejsでは、イベントキューからタスクを取得して実行したり、I/O操作をバックグラウンドスレッドのプールに渡して操作し続ける再帰スレッドがあり、この再帰スレッドの各実行がポーリングセッションとみなされる。

(2).setImmediate()の利用について
即時タイマーはすぐに仕事をします。イベントがポーリングされた後に実行され、ポーリングがブロックされるのを防ぐために、一度に1つだけ呼び出されます。

<ブロッククオート

詳細はこちら Window.setImmediate() - Web APIs | MDN

(3).Process.nextTick()の使用について

setImmediate()と同じ順番で実行されるのではなく、イベントポーリングの前に実行され、I/O飢餓を防ぐため、デフォルトでprocess.maxTickDepth=1000があり、イベントキューのループごとに実行できるnextTick()イベント数を制限しています。

<ブロッククオート

詳細はこちら process process|Node.js API ドキュメント

要約すると

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

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

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

並列、直列など、多くのapi(マルチタスク)を搭載したasyncオブジェクトを公開する。

// Unlike series/waterfall, tasks are executed side-by-side and callback is not executing the next task. 
async.parallel([
    function(callback){
        callback(null,'task1')
    },
    function(callback){
        callback(null,'task2')
    },
],(err,data)=>{
    console.log('data',data)
})


//execute the tasks in the array in order, no callback will execute the next task
async.series([
    function(callback){
        // do some stuff ...
        callback(null, 'one');
    },
    function(callback){
        // do some more stuff ...
        callback(null, 'two');
    }
],
// optional callback
function(err, results){
    // results is now equal to ['one', 'two']
});


<ブロッククオート

 async.jsの詳細です。 async-js - npm