に この記事の内容を学ぶ前に、まず非同期の概念を理解する必要があります。まず強調すべきことは、非同期と並列には本質的な違いがあるということです。
CPU
の複数のコア、複数のCPU
、または複数の物理ホスト、さらには複数のネットワークで実行されることを意味します。CPU
現在のタスクを一時的に保留し、最初に次のタスクを処理し、前のタスクのコールバック通知を受信した後、前のタスクに戻って実行を継続することを意味します。 2番目のスレッドに参加します。おそらく、並列処理、同期処理、非同期処理を図で説明する方がわかりやすいでしょう。処理する必要がある 2 つのタスク A と B があるとします。並列処理、同期処理、および非同期処理の方法は、次のとおりです。次の図:
JavaScript
は多くの非同期関数が用意されており、これらの関数を使用すると、タスク (関数) の実行をすぐに開始できますが、タスクは後で完了します。はわかりません。
たとえば、 setTimeout
関数は非常に典型的な非同期関数です。さらに、 fs.readFile
とfs.writeFile
も非同期関数です。
ファイルコピー関数copyFile(from,to)
カスタマイズするなど、非同期タスクのケースを自分で定義できます:
const fs = require('fs') 関数 copyFile(from, to) { fs.readFile(from, (err, data) => { if (エラー) { コンソールログ(エラーメッセージ) 戻る } fs.writeFile(to, data, (err) => { if (エラー) { コンソールログ(エラーメッセージ) 戻る } console.log('コピーが完了しました') }) })関数 copyFile は
、
copyFile
パラメータfrom
からファイル データを読み取り、次にパラメータto
が指すファイルにデータを書き込みます。
次のようにcopyFile
を呼び出すことができます:
copyFile('./from.txt','./to.txt')//ファイルをコピーします
この時点でcopyFile(...)
の後に他のコードがある場合、プログラムはコピーしません。 wait copyFile
の実行は終了しますが、プログラムはファイル コピー タスクがいつ終了するかを気にしません。
copyFile('./from.txt','./to.txt') // 次のコードは、上記のコードの実行が終了するのを待ちません...
ここまではすべて正常に見えますが、 copyFile(...)
の後にファイル./to.txt
に直接アクセスすると、 copyFile(...)
関数の内容はどうなりますか?
これではコピーされたコンテンツは読み取られません。単にこれを実行します:
copyFile('./from.txt','./to.txt') fs.readFile('./to.txt',(err,data)=>{ ... })
プログラムを実行する前に./to.txt
ファイルが作成されていない場合は、次のエラーが表示されます:
PS E:CodeNodedemos 3-callback> node .index.js
終了した
コピーが完了しました
PS E:CodeNodedemos 3-callback> ノード .index.js
エラー: ENOENT: そのようなファイルまたはディレクトリはありません。「E:CodeNodedemos 3-callbackto.txt」を開いてください。
コピー完了
./to.txt
が存在しても、コピーした内容は読み取れません。
この現象の原因は、 copyFile(...)
が非同期で実行されるため、プログラムがcopyFile(...)
関数を実行した後、コピーの完了を待たずに直接下方向に実行するため、ファイルが破損することです。 ./to.txt
が存在しないエラー、またはファイルの内容が空です (ファイルが事前に作成されている場合) エラーが表示されます。
コールバック関数の非同期関数の具体的な実行終了時刻は決定できません。たとえば、 readFile(from,to)
関数の実行終了時刻は、 from
のファイルのサイズに依存する可能性が高くなります。
では、問題は、どのようにしてcopyFile
実行の終わりを正確に特定し、 to
ファイルの内容を読み取ることができるかということです。
これにはコールバック関数を使用する必要があります。次のようにcopyFile
関数を変更できます。
function copyFile(from, to, callback) { fs.readFile(from, (err, data) => { if (エラー) { コンソールログ(エラーメッセージ) 戻る } fs.writeFile(to, data, (err) => { if (エラー) { コンソールログ(エラーメッセージ) 戻る } console.log('コピーが完了しました') callback()//コピー操作が完了するとコールバック関数が呼び出されます}) }) この
ようにして、ファイルのコピーが完了した直後にいくつかの操作を実行する必要がある場合は、これらの操作をコールバック関数に書き込むことができます。
function copyFile(from, to, callback) { fs.readFile(from, (err, data) => { if (エラー) { コンソールログ(エラーメッセージ) 戻る } fs.writeFile(to, data, (err) => { if (エラー) { コンソールログ(エラーメッセージ) 戻る } console.log('コピーが完了しました') callback()//コピー操作が完了するとコールバック関数が呼び出されます}) }) } copyFile('./from.txt', './to.txt', function () { //コールバック関数を渡し、「to.txt」ファイルの内容を読み取り、出力 fs.readFile('./to.txt', (err, data) => { if (エラー) { コンソールログ(エラーメッセージ) 戻る } console.log(data.toString()) }) })
./from.txt
ファイルを準備している場合は、上記のコードを直接実行できます:
PS E:CodeNodedemos 3-callback> node .index.js
コピーが完了しました
コミュニティ「Xianzong」に参加して、私と一緒に不滅を育ててください。コミュニティのアドレス: http://t.csdn.cn/EKf1h
このプログラミング方法は、「コールバックベース」の非同期プログラミング スタイルと呼ばれ、非同期で実行される関数はコールバック パラメーターを提供する必要があります。タスク終了後に呼び出すために使用されます。
このスタイルはJavaScript
プログラミングで一般的です。たとえば、ファイル読み取り関数fs.readFile
とfs.writeFile
はすべて非同期関数です。
コールバック関数は、非同期作業が完了した後、後続の処理を正確に処理できます。複数の非同期操作を順番に実行する必要がある場合は、コールバック関数をネストする必要があります。
ケースシナリオ:
ファイル A とファイル B を順番に読み取るためのコード実装:
fs.readFile('./A.txt', (err, data) => { if (エラー) { コンソールログ(エラーメッセージ) 戻る } console.log('ファイル A の読み取り: ' + data.toString()) fs.readFile('./B.txt', (err, データ) => { if (エラー) { コンソールログ(エラーメッセージ) 戻る } console.log("ファイル B を読み取ります: " + data.toString()) }) })
実行効果:
PS E:CodeNodedemos 3-callback> node .index.js
ファイル A を読む: 不滅の宗派は非常に優れていますが、ファイル B を読む: 不滅の宗派に参加したい場合は、リンクが必要です。
http://t.csdn.cn/H1faI
コールバックにより、ファイル A を読み取った直後にファイル B を読み取ることができます。
ファイル B の後にファイル C の読み取りを続けたい場合はどうすればよいでしょうか?これには、引き続きコールバックをネストする必要があります。
fs.readFile('./A.txt', (err, data) => {//First callback if (err) { コンソールログ(エラーメッセージ) 戻る } console.log('ファイル A の読み取り: ' + data.toString()) fs.readFile('./B.txt', (err, data) => {//2 番目のコールバック if (err) { コンソールログ(エラーメッセージ) 戻る } console.log("ファイル B を読み取ります: " + data.toString()) fs.readFile('./C.txt',(err,data)=>{//3 番目のコールバック... }) }) つまり
、複数の非同期操作を連続して実行したい場合、複数の層のネストされたコールバックが必要になります。これは、層の数が少ない場合には効果的ですが、ネストの回数が多すぎると、いくつかの問題が発生します。
コールバック規約
実際、 fs.readFile
のコールバック関数のスタイルは例外ではなく、 JavaScript
の一般的な規約です。将来的には多数のコールバック関数をカスタマイズする予定なので、この規則に従って、適切なコーディング習慣を身に付ける必要があります。
規則では、
callback
の最初のパラメータはエラーのために予約されています。エラーが発生すると、 callback(err)
が呼び出されます。callback(null, result1, result2,...)
が呼び出されます。上記の規則に基づいて、コールバック関数にはエラー処理と結果受信の 2 つの機能があります。たとえば、 fs.readFile('...',(err,data)=>{})
のコールバック関数はこの規則に従います。
さらに深く掘り下げなければ、コールバックに基づく非同期メソッド処理は、これを処理する非常に完璧な方法のように思えます。問題は、非同期動作が次々と行われる場合、コードは次のようになることです。
fs.readFile('./a.txt',(err,data)=>{ if(エラー){ コンソールログ(エラーメッセージ) 戻る } //結果の読み取り操作 fs.readFile('./b.txt',(err,data)=>{ if(エラー){ コンソールログ(エラーメッセージ) 戻る } //結果の読み取り操作 fs.readFile('./c.txt',(err,data)=>{ if(エラー){ コンソールログ(エラーメッセージ) 戻る } //結果の読み取り操作 fs.readFile('./d.txt',(err,data)=>{ if(エラー){ コンソールログ(エラーメッセージ) 戻る } ... }) }) }) })
上記のコードの実行内容は次のとおりです。
呼び出しの数が増えると、コードのネスト レベルがますます深くなり、条件ステートメントがさらに多くなり、コードが常に右にインデントされ、読みにくくなり、保守しにくくなります。
この右への継続的な成長 (右へのインデント) 現象を、私たちは「コールバック地獄」または「破滅のピラミッド」と呼んでいます。
fs.readFile('a.txt',(err,data)=>{ fs.readFile('b.txt',(err,data)=>{ fs.readFile('c.txt',(err,data)=>{ fs.readFile('d.txt',(err,data)=>{ fs.readFile('e.txt',(err,data)=>{ fs.readFile('f.txt',(err,data)=>{ fs.readFile('g.txt',(err,data)=>{ fs.readFile('h.txt',(err,data)=>{ ... /* 地獄への門 ===> */ }) }) }) }) }) }) }) })
上記のコードは非常に規則的であるように見えますが、これは単なる理想的な状況にすぎません。通常、ビジネス ロジックには多数の条件文、データ処理操作、その他のコードがあり、現在の美しい順序が崩れてコードが作成されます。維持するのが難しい。
幸いなことに、 JavaScript
複数のソリューションを提供しており、 Promise
最良のソリューションです。