使用Node.js检查文件是否完全写入

3
我需要一些帮助,了解如何在JavaScript中处理以下任务: 我有一个应用程序,使用Jimp进行图像处理和node-sprite-generator。这一切都在node.js上下文中运行。我将几张图片加载到Jimp中,对图像进行某些操作,然后使用nodejs文件模块将其写回到我的文件系统中。然后,我会将新创建的图像粘贴到node-sprite-generator中。问题是,并非所有图像都在此时创建/编写。虽然用于创建精灵表的代码在Jimp返回后立即运行,但我认为Jimp正在处理所有图像并返回一个承诺。结果是,用于创建精灵表的代码被执行,但堆栈尚未完成。

我尝试使用fs.stat()和属性mtime测试文件是否已写入,例如

 if (stat.mtime.getTime() === prev.mtime.getTime())

但是有时候会出现错误,当文件在这个时候没有被创建。另外:我需要一种方法来检查图像是否已经完全写入,并处理当图像路径当前不可用的情况。
function resize(img) {
    Jimp.read(img.path).then(function (err, file) {
        if (err) throw err;
        file.resize(200, 200)
            .quality(70)
            .write(newPath); //newPath for simplicity
    });
}


function rec(imgObjArray) {
    if(_.isEmpty(imgObjArray)) return;  
    resize(imgObjArray.pop());
//correct mistake in function call from fn() to rec()
    rec(imgObjArray);
}

rec(imgObjArray); //imgObjArray === [img,img,img....]

//nsg() does not work because not all images are written at this time
     nsg({
            src: [
                'out/images/desktop/*.jpg'
            ],
            spritePath: 'out/images/desktop/sprite.jpg',,
            compositor: 'jimp'
        }, function (err) {
            console.log('Sprite generated!');
        })

我认为首先必须检查给定路径下的图像是否存在,然后再检查写入是否完成。但是当我使用fs.access(path [,mode],callback)创建一个函数时,如果此时文件尚未创建,我会收到错误信息。


如果你有一个 Promise,你需要使用它来在第一个操作完成后执行下一个操作。为了提供有用的指导,看到你的代码或至少其中涉及任务转换的部分会很有帮助。 - Jason
我也这么想,所以我在jimp队列中创建了一个then()函数,其中包含执行sprite-sheet代码的fn-call。但是我遇到了相同的问题。 - Zantinger
你需要展示你的代码。关于代码的问题必须在问题中包含相关的代码。如果您向我们展示代码,我们可能只需要几分钟就能帮助您解决问题。 - jfriend00
2个回答

4
您在这里有同步和异步代码混合。我将尝试在注释中描述正在发生的情况:
首先,您的函数定义-您正在触发异步操作,但没有正确处理它们的完成。
// I've re-spaced the code slightly and removed your comments so mine stand out
function resize(img) {
    Jimp.read(img.path).then(function (err, file) {
        // this code only executes after the file is done reading, but this
        // is an asynchronous action - it doesn't hold up execution
        if (err) throw err;
        file.resize(200, 200).quality(70).write(newPath);
        // .write() is presumably *also* an asynchronous action - if you want
        // something to happen only *after* it's been written, it needs to be in
        // a callback or promise on the write method
    });

    // I added this explicitly - after you *start* your Jimp.read, you *immediately*
    // return from this function, *before* the read is completed.  If you want
    // something to happen only *after* your read and write, you either need to
    // return the promise so you can act on it, or put the further actions in a callback
    return undefined;
}

function rec(imgObjArray) {
    if(_.isEmpty(imgObjArray)) return; 
    // resize() runs and returns *before* the file is read, resized, and written
    resize(imgObjArray.pop());
    // I don't know what fn() is, it's not defined here - presumably it's not important
    fn(imgObjArray);
}

然后是您的过程调用:

// this fires off and completes immediately, having initiated the asynchronous methods
rec(imgObjArray);

// you call this on the assumption that all of your code above has completed, but since
// it's asynchronous, that's not true, you get here with *none* of your images completed
nsg({
    src: [
        'out/images/desktop/*.jpg'
    ],
    spritePath: 'out/images/desktop/sprite.jpg',
    compositor: 'jimp'
}, function (err) {
    console.log('Sprite generated!');
});

你有两个选项:
如果file.write()是同步调用,你只需要返回 promise 并在其上进行操作:
function resize(img) {
    // by *returning* this call, we're actually returning the promise, we can act on
    // in the future
    return Jimp.read(img.path).then(function (err, file) {
        if (err) throw err;
        file.resize(200, 200).quality(70).write(newPath);
    });
}

function rec(imgObjArray) {
    if(_.isEmpty(imgObjArray)) return; 
    // the result of resize is now a promise
    return resize(imgObjArray.pop()).then(function(err) {;
        // again, assuming `fn()` is synchronous...
        fn(imgObjArray);
    });
}

// now the result of *this* call is a promise, which you can use to control
// the timing of your next call
rec(imgObjArray).then(function(err) {
    // now this will only run after all of the previous calls have executed
    nsg({
        src: [
            'out/images/desktop/*.jpg'
        ],
        spritePath: 'out/images/desktop/sprite.jpg',
        compositor: 'jimp'
    }, function (err) {
        console.log('Sprite generated!');
    });
});

如果 Promise 语法不正确,那我很抱歉,因为自从它们变得无处不在以来,我就没有积极地使用过 Node。

即使您的子调用是异步的,也很可能有一种方法可以以相同的方式使用 Promise,只是我现在没有准备好。

否则,您可以将回调传递到您的函数中:

function resize(img, cb) {
    // ... you get the idea...
        file.resize(200, 300).quality(70).write(newPath, cb);
}

function rec(imgObjArray, cb) {
    // ... you get the idea...
    resize(imgObjArray.pop(), cb);
}

rec(imgObjArray, function(err, response) {
    nsg({
        src: [
            'out/images/desktop/*.jpg'
        ],
        spritePath: 'out/images/desktop/sprite.jpg',
        compositor: 'jimp'
    }, function (err) {
        console.log('Sprite generated!');
    });
});

希望这能帮到你!

它可以工作!非常感谢@Jason。附注:fn()函数实际上是一个rec()调用。这将使函数本身被调用,直到数组imgObj为空为止。但最重要的是,你帮我找到了一个可行的解决方案。谢谢。 - Zantinger
好的,另一个建议 - 递归调用比只是循环遍历数组效率低,因此您的 rec() 函数应该只进行简单循环,而不是调用自身并弹出元素。通过您当前的作用域设置,最终结果将是相同的。不管怎样,我很高兴能帮到你! - Jason

-1

如果 +if *2+2-2 是你的问题的答案


1
你确定你在回答上面的问题吗? - Gert Arnold
如果你有新的问题,请通过点击[提问]按钮进行提问。如果需要提供上下文,可以包含此问题的链接。- 来自审核 - Tirolel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接