在 node.js 中复制文件的最快方式

Project that I am working at (node.js) implies a lot of operations with file system (copying/reading/writing etc). I'd like to know, what methods are the fastest, and I'd be happy for some advice.

转载于:https://stackoverflow.com/questions/11293857/fastest-way-to-copy-file-in-node-js

This is a good way to copy a file in one line of code using streams:

var fs = require('fs');

fs.createReadStream('test.log').pipe(fs.createWriteStream('newLog.log'));

Mike Schilling's solution with error handling with a short-cut for the error event handler.

function copyFile(source, target, cb) {
  var cbCalled = false;

  var rd = fs.createReadStream(source);
  rd.on("error", done);

  var wr = fs.createWriteStream(target);
  wr.on("error", done);
  wr.on("close", function(ex) {
    done();
  });
  rd.pipe(wr);

  function done(err) {
    if (!cbCalled) {
      cb(err);
      cbCalled = true;
    }
  }
}

I was not able to get the createReadStream/createWriteStream method working for some reason, but using fs-extra npm module it worked right away. I am not sure of the performance difference though.

fs-extra

npm install --save fs-extra

var fs = require('fs-extra');

fs.copySync(path.resolve(__dirname,'./init/xxx.json'), 'xxx.json');

Fast to write and convenient to use, with promise and error management.

function copyFile(source, target) {
  var rd = fs.createReadStream(source);
  var wr = fs.createWriteStream(target);
  return new Promise(function(resolve, reject) {
    rd.on('error', reject);
    wr.on('error', reject);
    wr.on('finish', resolve);
    rd.pipe(wr);
  }).catch(function(error) {
    rd.destroy();
    wr.end();
    throw error;
  });
}

Same with async/await syntax:

async function copyFile(source, target) {
  var rd = fs.createReadStream(source);
  var wr = fs.createWriteStream(target);
  try {
    return await new Promise(function(resolve, reject) {
      rd.on('error', reject);
      wr.on('error', reject);
      wr.on('finish', resolve);
      rd.pipe(wr);
    });
  } catch (error) {
    rd.destroy();
    wr.end();
    throw error;
  }
}

If you don't care about it being async, and aren't copying gigabyte-sized files, and would rather not add another dependency just for a single function:

function copySync(src, dest) {
  if (!fs.existsSync(src)) {
    return false;
  }

  var data = fs.readFileSync(src, 'utf-8');
  fs.writeFileSync(dest, data);
}

benweet's solution checking visibility of the file before copy:

function copy(from, to) {
    return new Promise(function (resolve, reject) {
        fs.access(from, fs.F_OK, function (error) {
            if (error) {
                reject(error);
            } else {
                var inputStream = fs.createReadStream(from);
                var outputStream = fs.createWriteStream(to);

                function rejectCleanup(error) {
                    inputStream.destroy();
                    outputStream.end();
                    reject(error);
                }

                inputStream.on('error', rejectCleanup);
                outputStream.on('error', rejectCleanup);

                outputStream.on('finish', resolve);

                inputStream.pipe(outputStream);
            }
        });
    });
}

Mike's solution, but with promises:

const FileSystem = require('fs');

exports.copyFile = function copyFile(source, target) {
    return new Promise((resolve,reject) => {
        const rd = FileSystem.createReadStream(source);
        rd.on('error', err => reject(err));
        const wr = FileSystem.createWriteStream(target);
        wr.on('error', err => reject(err));
        wr.on('close', () => resolve());
        rd.pipe(wr);
    });
};

Improvement of one other answer.

Features:

  • If the dst folders do not exist, it will automatically create it. The other answer will only throw errors.
  • It returns a promise, which makes it easier to use in a larger project.
  • It allows you to copy multiple files, and the promise will be done when all of them are copied.

Usage:

var onePromise = copyFilePromise("src.txt", "dst.txt");
var anotherPromise = copyMultiFilePromise(new Array(new Array("src1.txt", "dst1.txt"), new Array("src2.txt", "dst2.txt")));

Code:

function copyFile(source, target, cb) {
    console.log("CopyFile", source, target);

    var ensureDirectoryExistence = function (filePath) {
        var dirname = path.dirname(filePath);
        if (fs.existsSync(dirname)) {
            return true;
        }
        ensureDirectoryExistence(dirname);
        fs.mkdirSync(dirname);
    }
    ensureDirectoryExistence(target);

    var cbCalled = false;
    var rd = fs.createReadStream(source);
    rd.on("error", function (err) {
        done(err);
    });
    var wr = fs.createWriteStream(target);
    wr.on("error", function (err) {
        done(err);
    });
    wr.on("close", function (ex) {
        done();
    });
    rd.pipe(wr);
    function done(err) {
        if (!cbCalled) {
            cb(err);
            cbCalled = true;
        }
    }
}

function copyFilePromise(source, target) {
    return new Promise(function (accept, reject) {
        copyFile(source, target, function (data) {
            if (data === undefined) {
                accept();
            } else {
                reject(data);
            }
        });
    });
}

function copyMultiFilePromise(srcTgtPairArr) {
    var copyFilePromiseArr = new Array();
    srcTgtPairArr.forEach(function (srcTgtPair) {
        copyFilePromiseArr.push(copyFilePromise(srcTgtPair[0], srcTgtPair[1]));
    });
    return Promise.all(copyFilePromiseArr);
}

all above solutions that do not check an existence of a source file are dangerous... e.g.

fs.stat(source, function(err,stat) { if (err) { reject(err) }

otherwise there is a risk in a scenario in case source and target are by a mistake replaced, your data will be permanently lost without noticing any error.

Since Node.js 8.5.0 we have new fs.copyFile and fs.copyFileSync methods.

Usage Example:

var fs = require('fs');

// destination.txt will be created or overwritten by default.
fs.copyFile('source.txt', 'destination.txt', (err) => {
    if (err) throw err;
    console.log('source.txt was copied to destination.txt');
});

Why not use nodejs built in copy function?

It provide both async and sync version:

const fs = require('fs');

// destination.txt will be created or overwritten by default.
fs.copyFile('source.txt', 'destination.txt', (err) => {
  if (err) throw err;
  console.log('source.txt was copied to destination.txt');
});

https://nodejs.org/api/fs.html#fs_fs_copyfilesync_src_dest_flags