初期の Web アプリケーションでは、バックグラウンドと対話するときに、フォームを送信し、ページが更新された後にユーザーにフィードバック結果を提供する必要がありました。ページの更新プロセス中、背景は HTML コードを返します。この HTML のコンテンツのほとんどは基本的に前のページと同じであるため、必然的にトラフィックの無駄が発生し、ページの応答時間も長くなります。 Web アプリケーションのエクスペリエンスはクライアント アプリケーションほど良くないと人々に感じさせるでしょう。
2004 年に、AJAX (「非同期 JavaScript および XML」) テクノロジーが誕生し、Web アプリケーションのエクスペリエンスが向上しました。2006 年に jQuery が登場し、Web アプリケーションの開発エクスペリエンスが新たなレベルに引き上げられました。
JavaScript 言語のシングルスレッド機能により、イベントのトリガーであっても AJAX であっても、非同期タスクはコールバックを通じてトリガーされます。複数の非同期タスクを線形に処理する場合、コードには次のような状況が表示されます。
getUser(トークン, 関数(ユーザー) {
getClassID(ユーザー, 関数 (id) {
getClassName(id, function (name) {
console.log(name)
})
})
})
この種のコードを「コールバック地獄」と呼ぶことがあります。
イベントとコールバック
ご存知のとおり、JavaScript のランタイムは単一のスレッドで実行され、イベント モデルに基づいて非同期タスクをトリガーします。共有メモリのロックの問題を考慮する必要はなく、バインドされたイベントは適切な順序でトリガーされます。JavaScript の非同期タスクを理解するには、まず JavaScript のイベント モデルを理解する必要があります。
これは非同期タスクであるため、将来 (指定された時間が終了したとき、またはイベントがトリガーされたとき) に実行するコードを整理する必要があります。通常、このコードを匿名関数 (通常はコールバックと呼ばれます) に置きます。関数。
setTimeout(関数() {
// 指定された時間が終了するとコールバックがトリガーされます
},800)
window.addEventListener(“サイズ変更”, function() {
// ブラウザ ウィンドウが変更されたときにコールバックがトリガーされる
})
今後の実行
前述したように、コールバック関数の動作は将来のことであり、コールバックで使用される変数はコールバック宣言の段階では固定されていません。
for (var i = 0; i < 3; i++) {
setTimeout(関数() {
console.log("i =", i)
}、100)
}
ここでは非同期タスクが3つ連続して宣言されており、変数iの結果は100ミリ秒後に出力されますが、通常のロジックでは0、1、2の3つの結果が出力されるはずです。
しかし、これは事実ではなく、コールバック関数の実際の実行時間は将来のことであるため、これは JavaScript に初めて触れるときに遭遇する問題でもあり、i の出力値は実行時の値になります。ループの終わりと 3 つの非同期タスク 結果は一貫しており、3 つの i = 3 が出力されます。
この問題を経験した学生は、ローカル変数を閉じるか再宣言することでこの問題を解決できることを知っています。
イベントキュー
イベントがバインドされた後、すべてのコールバック関数が保存され、プロセスの実行中に、別のスレッドがこれらの非同期に呼び出されるコールバックをスケジュールして処理します。「トリガー」条件が満たされると、コールバック関数は対応するイベントキュー (ここでは単にキューとして理解されています。実際にはマクロタスクとマイクロタスクの 2 つのイベントキューがあります)。
トリガー条件は通常、次の状況で満たされます。
HarmonyOS公式の戦略的協力と共同構築 —— HarmonyOSテクノロジーコミュニティ
クリック、移動、フォーカスが外れるなどの DOM 関連の操作によってトリガーされるイベント。
IO関連操作、ファイル読み取り完了、ネットワークリクエスト終了など。
時間関連の操作。スケジュールされたタスクの合意時間に達する。
上記の動作が発生すると、コード内で事前に指定したコールバック関数がタスク キューに入れられ、メイン スレッドがアイドル状態になると、その中のタスクが先入れ先出しプロセスに従って 1 つずつ実行されます。 。新しいイベントがトリガーされると、そのイベントはコールバックに戻されるなどの処理が行われるため、JavaScript のこのメカニズムは通常「イベント ループ メカニズム」と呼ばれます。
for (var i = 1; i <= 3; i++) {
定数 x = i
setTimeout(関数() {
console.log(`第${x}个setTimout被执行`)
}、100)
}
実行シーケンスがキューの先入れ先出し機能を満たしており、最初のステートメントが最初に実行されることがわかります。
スレッドのブロック
JavaScript のシングルスレッドの特性により、タイマーは実際には信頼できません。コードがブロック状況に遭遇すると、イベントがトリガー時間に達したとしても、メインスレッドがアイドル状態になるまで待機してから実行されます。
const start = Date.now()
setTimeout(関数() {
console.log( 实际等待时间: ${Date.now() - start}ms
)
}、300)
// while ループはスレッドを 800ms ブロックします
while(Date.now() - 開始 < 800) {}
上記のコードでは、タイマーを 300ms に設定した後にコールバック関数がトリガーされていますが、コードがブロックされていない場合、通常の状況では 300ms 後の待ち時間が出力されます。
ただし、while ループを追加していないため、このループは 800 ミリ秒後に終了します。メイン スレッドがこのループによってブロックされているため、時間が経過してもコールバック関数が正常に実行されません。
約束
特にイベントコールバックのやり方はコーディング中にコールバック地獄を引き起こしやすいです。また、Promise は、パイプライン メカニズムに似た、非同期コードを作成するためのより直線的な方法を提供します。
// コールバック地獄
getUser(トークン, 関数(ユーザー) {
getClassID(ユーザー, 関数 (id) {
getClassName(id, function (name) {
console.log(name)
})
})
})
// 約束
getUser(トークン).then(関数(ユーザー) {
getClassID(ユーザー)を返す
}).then(関数 (id) {
getClassName(id) を返す
}).then(関数 (名前) {
console.log(名前)
}).catch(関数 (エラー) {
console.error('リクエスト例外', err)
})
Promise には多くの言語で同様の実装があり、JavaScript の開発中に、より有名なフレームワークである jQuery と Dojo も同様の実装を実装しました。2009 年に、Dojo.Defered の実装に基づいて CommonJS 仕様が発表され、Promise/A 仕様が提案されました。Node.js が誕生したのもこの年で、Node.js の実装の多くは CommonJS 仕様に基づいており、より馴染みのあるのはそのモジュール化スキームです。
Promise オブジェクトは初期の Node.js にも実装されていましたが、2010 年に Ry (Node.js の作成者) は Promise が比較的高レベルの実装であると考えており、Node.js の開発は当初 V8 エンジンに依存していました。では、V8 エンジンの Promise サポートはネイティブには提供されなかったため、以降の Node.js モジュールでは error-first コールバック スタイル (cb(error, result)) が使用されました。
const fs = require('fs')
// 最初のパラメータは Error オブジェクトです。空でない場合は、例外が発生したことを意味します
fs.readFile('./README.txt', function (err, バッファ) {
if (err !== null) {
return
}
console.log(buffer.toString())
})
この決定は、Node.js でのさまざまな Promise ライブラリの出現にもつながりました。より有名なものは Q.js と Bluebird です。Promiseの実装については以前に記事を書きましたので、興味のある方は「Promiseを実現するための教育」をご覧ください。
Node.js@8 より前は、V8 のネイティブ Promise 実装にはパフォーマンス上の問題があり、その結果、ネイティブ Promise のパフォーマンスは一部のサードパーティ Promise ライブラリよりも劣っていました。
したがって、低バージョンの Node.js プロジェクトでは、Promise がグローバルに置き換えられることがよくあります。
const Bulebird = require('bluebird')
global.Promise = Bulebird
ジェネレーター&co
ジェネレーター (generator) は ES6 によって提供される新しい関数タイプで、主にそれ自体を反復できる関数を定義するために使用されます。ジェネレーター関数は、 function * の構文を使用して構築できます。関数が実行されると、反復 (イテレーター) オブジェクトが返されます。このオブジェクトには next() メソッドがあります。next() メソッドが呼び出されるたびに、 next() メソッドを再度呼び出すまで、 yield キーワードの前で一時停止します。
関数 * forEach(配列) {
const len = 配列.長さ
for (let i = 0; i < len; i ++) {
yield i;
}
}
const it = forEach([2, 4, 6])
it.next() // { 値: 2、完了: false }
it.next() // { 値: 4、完了: false }
it.next() // { 値: 6、完了: false }
it.next() // { 値: 未定義、完了: true }
next() メソッドは、2 つの属性値を持つオブジェクトを返し、完了します。
value: 降伏後の値を示します。
完了: 関数が実行されたかどうかを示します。
ジェネレーター関数は割り込み実行の特性があるため、ジェネレーター関数を非同期操作のコンテナとみなし、Promise オブジェクトのメソッドを使用して非同期ロジックの実行権を引き継ぎ、毎回の後に Promise オブジェクトを追加します。 yeild の場合、反復子は継続的に実行できます。
関数 * gen(トークン) {
const user = yield getUser(トークン)
const cId = yield getClassID(user)
const name = yield getClassName(cId)
console.log(名前)
}
const g = gen('xxxx-トークン')
// nextメソッドで返された値をPromiseオブジェクトとして実行
const { 値: Promise1 } = g.next()
promise1.then(user => {
// 2 番目の next メソッドで渡された値は、ジェネレーターの最初の yield キーワードの前の変数によって受け入れられます。
// プッシュバックの場合も同様で、3 番目の next メソッドの値は 2 番目の yield の前に変数によって受け入れられます。
// 最初の next メソッドの値のみが破棄されます
const {値:promise2} = gen.next(user).value
promise2.then(cId => {
const { value: promise3, done } = gen.next(cId).value
// 依次先后传递,直到 next 方法返回的 done 为 true
})
})
上記のロジックを抽象化して、各 Promise オブジェクトが正常に戻った後、自動的に next を呼び出し、実行が完了する (つまり、done が true) までイテレーター自体を実行させます。
関数 co(gen, …args) {
const g = gen(…args)
関数 next(データ) {
const { value: promise, done } = g.next(data)
if (done) return promise
promise.then(res => {
next(res) // 将 promise 的结果传入下一个 yield
})
}
next() // 自己実行を開始します
}
co(gen, 'xxxx-トークン')
これは koa の初期のコア ライブラリ co の実装ロジックですが、co はいくつかのパラメーターの検証とエラー処理を実行しています。ジェネレーターに co を追加すると、非同期プロセスがよりシンプルで読みやすくなります。これは開発者にとって間違いなく嬉しいことです。
非同期/待機
async/await は JavaScript の非同期変換のソリューションと言えます。実際、これは本質的に Generator の構文糖です。非同期ジェネレーター関数の前に async を追加し、ジェネレーター関数の yield を置き換えるだけです。 await 付き。
非同期関数 fun(トークン) {
const user = await getUser(トークン)
const cId = await getClassID(user)
const name = await getClassName(cId)
console.log(名前)
}
楽しい()
async 関数には自己実行プログラムが組み込まれており、await は Promise オブジェクトに限定されず、任意の値を指定できます。また、async/await のセマンティクスはジェネレーターの yield よりも明確であり、理解することができます。これが非同期操作であることが一目で分かります。
記事の出典: ネットワークの著作権は原作者に帰属します
上記のコンテンツは営利目的ではありません。知的財産権に関する問題が含まれる場合は、編集者にご連絡ください。すぐに対処します。