2017-01-10 2 views
0

Ich stelle ein Beispiel zusammen, das zeigt, wie ein einfaches synchrones Node.js-Programm in eine asynchrone Version umgewandelt werden kann, die async/await verwendet. Es sollte mehrere Zwischenschritte geben, beginnend mit einer normalen Callback-basierten Version, gefolgt von einer, die zwei Callbacks verwendet, einen für den normalen (Auflösungs-) Fall und einen anderen für den Fehler- (Zurückweisungs-) Fall, was dann zu Versprechungen führen würde.Einen Rückruf im Node-Stil in zwei Callbacks aufteilen

Die Aufgabe jeder Version ist ein leerer Ordner Kopie zu schaffen (was möglicherweise bereits vorhanden sind und es könnte Dateien enthalten) und kopieren Sie alle Dateien (so genannte file1.txt und file2.txt) im Ordner orig dort. Wenn ein Fehler irgendwo auftritt, sollte er explizit abgefangen werden, auf die Konsole gedruckt werden und das Programm sollte nicht weiter fortfahren.

Die Version mit normalen Fehler-erste Rückrufe funktioniert gut, aber ich stieß auf ein Problem mit der Split-Callback-Version. Es kopiert nur Datei2.txt, aber nicht Datei1.txt. Hier

ist der Code verwende ich für die Transformation der fs-Funktionen:

const fs = require('fs'); 

fs.exists = function(path, callback) { 
    fs.stat(path, (err, stats) => { 
     if (err) { 
      callback(null, false); 
     } else { 
      callback(null, true); 
     } 
    }); 
}; 

function splitCallback(f) { 
    return (...params) => { 
     reject = params[params.length - 2]; 
     resolve = params[params.length - 1]; 
     params = params.slice(0, params.length - 2); 
     f(...params, (err, data) => { 
      if (err) { 
       reject(err); 
      } else { 
       resolve(data); 
      } 
     }); 
    }; 
} 

const sfs = {}; 
const functionNames = ['exists', 'readdir', 'unlink', 'mkdir', 'readFile', 'writeFile']; 
for (const functionName of functionNames) { 
    sfs[functionName] = splitCallback(fs[functionName].bind(fs)); 
} 

und das ist das eigentliche Beispiel jene Funktionen:

function handleError(err) { 
    console.error(err); 
} 

function initCopyDirectory(callback) { 
    sfs.exists('copy', handleError, exists => { 
     if (exists) { 
      sfs.readdir('copy', handleError, filenames => { 
       let fileCount = filenames.length; 
       if (fileCount === 0) { 
        callback(); 
       } 
       for (const filename of filenames) { 
        sfs.unlink(`copy/${filename}`, handleError,() => { 
         fileCount--; 
         if (fileCount === 0) { 
          callback(); 
         } 
        }); 
       } 
      }); 
     } else { 
      sfs.mkdir('copy', handleError,() => callback); 
     } 
    }); 
} 

function copyFiles() { 
    // sfs.readdir('orig', handleError, filenames => { 
    //  for (const filename of filenames) { 
    //   console.log(filename); 
    //   sfs.readFile(`orig/${filename}`, handleError, data => { 
    //    console.log('reading', filename); 
    //    sfs.writeFile(`copy/${filename}`, data, handleError,() => { 
    //     console.log('writing', filename); 
    //    }); 
    //   }); 
    //  } 
    // }); 
    sfs.readdir('orig', handleError, filenames => { 
     for (const filename of filenames) { 
      fs.readFile(`orig/${filename}`, (err, data) => { 
       if (err) { 
        handleError(err); 
       } else { 
        sfs.writeFile(`copy/${filename}`, data, handleError,() => {}); 
       } 
      }); 
     } 
    }); 
} 

function main() { 
    initCopyDirectory(copyFiles); 
} 

main(); 

Wie es hier geschrieben wird es richtig funktioniert (Node-Version 7.4.0 für Windows), aber wenn ich die Kommentare in der copyFiles-Funktion vertausche (wodurch readFile geändert wird), wird nur eine Datei kopiert und ich erhalte die folgende Ausgabe:

file1.txt 
file2.txt 
reading file2.txt 
writing file2.txt 
writing file2.txt 

Was ist das Problem?

Antwort

0

Versuchen Sie, diese anstelle des kommentierten Code:

 for (const filename of filenames) { 
     (function(filename){ 
      console.log(filename); 
      sfs.readFile(`orig/${filename}`, handleError, data => { 
       console.log('reading', filename); 
       sfs.writeFile(`copy/${filename}`, data, handleError,() => { 
        console.log('writing', filename); 
       }); 
      }); 
     })(filename) 
    } 

Das Problem ist, dass Sie innerhalb einer asynchronen Funktionen für Schleife ausgeführt werden und erwarten sie synchron zu verhalten. Zu dem Zeitpunkt, als sfs.writeFile aufgerufen wird (nachdem sfs.readFile ausgeführt wurde), ist die for-Schleife schon lange fertig ausgeführt und Sie haben nur noch den letzten Dateinamen, Datei2 übrig. Wenn Sie alles in die for-Schleife in einer Schließung einschließen, behalten Sie die richtigen Werte bei.

Hier ist ein einfacheres Beispiel:

for (var i = 0; i < 10 ; i++) { 
    setTimeout(function(){ 
     console.log(i) 
    }, 100) 
} 

druckt 10 10mal, weil durch die Zeit, die Timeout ausführt (0,1 Sekunden), die for-Schleife ist bereits getan, während der folgende Code wird für die Zahlen 0 bis gedruckt 9 weil die ursprünglichen Werte durch den Verschluss erhalten bleiben. (versuchen Sie es selbst)

for (var i = 0; i < 10 ; i++) { 
    (function(i){ 
     setTimeout(function(){ 
      console.log(i) 
     }, 100) 
    })(i) 
} 
+0

Das dachte ich auch zuerst, aber ich bekomme das gleiche Ergebnis hier. Und laut [this] (http://exploringjs.com/es6/ch_for-of.html#_iteration-variables-const-declarations-versus-var-declarations) sollte ich eine neue Bindung für const-deklarierte Variablen in a bekommen for-of-Schleife in jeder Iteration. – Ignavia

+0

@Ignavia was meinst du mit "eine frische Bindung". Die Verwendung von const ändert nicht die Tatsache, dass Sie die Bindung in asynchronen Funktionen innerhalb der for-Schleife nicht beibehalten können, die immer schneller als jede asynchrone Funktion ist. – AllTheTime

+0

Verwenden Sie Ihr einfaches Beispiel in leicht veränderter Form: const a = [1,2,3, 4,5,6,7,8,9,10]; für (var i von a) { setTimeout (Funktion() { console.log (i); }, 100); } erzeugt zehnmal 10 als Ausgabe, während const a = [1,2,3,4,5,6,7,8,9,10]; für (const i von a) { setTimeout (Funktion {) { console.log (i); }, 100); } erzeugt die Zahlen von 1 bis 10. Es gibt eine besser formatierte Version unter dem Link im ersten Kommentar. – Ignavia

0

Das Problem war, dass ich vergessen habe, const vor den Variablendeklarationen in splitCallback zu setzen. Dies machte sie zu globalen Variablen, die immer wieder außer Kraft gesetzt wurden. Das Aktivieren des strikten Modus hätte stattdessen einen Fehler ausgelöst. Hier ist der korrekte Code:

function splitCallback(f) { 
    return (...params) => { 
     const input    = params.slice(0, params.length - 2); 
     const [reject, resolve] = params.slice(params.length - 2); 
     f(...input, (err, ...output) => { 
      if (err) { 
       reject(err); 
      } else { 
       resolve(...output); 
      } 
     }); 
    }; 
}