JavaScript如何更好地编写嵌套回调?

4
我有三层回调,就像这样:

    app.post('/', (req, res) => {
        var filename = `outputs/${Date.now()}_output.json`;
        let trainInput = req.files.trainInput;
        let trainOutput = req.files.trainInput;
        let testInput = req.files.trainInput;

        //first
        trainInput.mv(`inputs/${req.body.caseName}/train_input.csv`, function (err) {
            if (err) return res.status(500).send(err);
            //second
            trainOutput.mv(`inputs/${req.body.caseName}/train_output.csv`, function (err) {
                if (err) return res.status(500).send(err);
                //third
                testInput.mv(`inputs/${req.body.caseName}/test_input.csv`, function (err) {
                    if (err) return res.status(500).send(err);

                    res.send('success');
                });
            });
        });   
    });

在这种情况下,只有3个文件上传。在另一种情况下,我有超过10个文件上传,这会导致10层回调。我知道这是由于JavaScript异步引起的。

对于这种情况,有没有办法编写更优雅的代码?因为当有10层回调时,代码看起来很奇怪。

谢谢。


我会使用 Promiseasync/await 来整理这个问题。 - Anders Marzi Tornblad
2
看看 Promise。你可能想从这里开始了解:https://developers.google.com/web/fundamentals/primers/promises它们是为像你描述的情况而设计的。 - LongHike
我使用npm包,它只有回调函数。 - yozawiratama
你需要它们排成一行吗?我的意思是,它们是一个工作流,一个接一个地排列,只有在出现错误时才会执行下一个。 - inetphantom
1
我使用npm包 - 什么包?它只有回调函数 - 这是个问题吗?将其转换成Promise或者使用支持Promise的包(这取决于你没有列出来的包)。 - Estus Flask
显示剩余2条评论
4个回答

6
您可以使用以下代码使您的代码看起来更好,并避免回调地狱。
app.post('/', async (req, res) => {
    var filename = `outputs/${Date.now()}_output.json`;
    let trainInput = req.files.trainInput;
    let trainOutput = req.files.trainInput;
    let testInput = req.files.trainInput;
    try {
        var result1 = await trainInput.mv(`inputs/${req.body.caseName}/train_input.csv`);
        var result2 = await trainInput.mv(`inputs/${req.body.caseName}/train_output.csv`);
        var result2 = await testInput.mv(`inputs/${req.body.caseName}/test_input.csv`);
        res.send('success');
    }
    catch (error) {
        res.status(500).send(error);
    }
});

请注意,async/await 只支持 "Node.js 7 及以上" 版本(参见 https://node.green/#ES2017-features-async-functions)。 - Eric Wong

3

你可以使函数返回一个Promise。

我建议只编写一个函数,因为你做了三次相同的事情。在这种情况下,我将函数称为“save”,但你可以自己命名。第一个参数是文件结尾,第二个参数是输出文件名。

function save(file, output) = return new Promise((resolve, reject) => {
  file.mv(`inputs/${req.body.caseName}/${output}`, err => 
  if (err) return reject(err)
  resolve()
})

Promise.all([
    save(req.files.trainInput, 'train_input.csv'),
    save(req.files.trainInput, 'train_output.csv'),
    save(req.files.trainInput, 'test_input.csv')
])
.then(_ => res.send(200))
.catch(err => res.send(400);


0
你使用的是哪个版本的Node?如果支持async/await,那么代码会更加简洁。
const moveCsv = (file, dest) => {
    return new Promise((resolve, reject) => {
        //third
        file.mv(dest, function (err) {
            if (err) reject(err);
            resolve();
        });
    })
}

app.post('/', async(req, res) => {
    try {
        var filename = `outputs/${Date.now()}_output.json`;

        const {
            trainInput,
            trainOutput,
            testInput
        } = req.files;

        const prefix = `inputs/${req.body.caseName}`;
        await moveCsv(trainInput, `${prefix}/train_input.csv`);
        await moveCsv(trainOutput, `${prefix}/train_output.csv`);
        await moveCsv(testInput, `${prefix}/test_input.csv`);
        res.send('success');
    } catch(err) {
        res.status(500).send(err);
    }
});

我这里也假设你的 trainInputtrainOutputtestOutput 并不是都指向 req.files.trainInput

只要注意到 await 调用的同步特性会阻塞线程即可。如果写入函数需要很长时间,你可以考虑将这些调用放到工作线程中。如果你对该服务器端点的请求快速且不频繁,那么这并不重要。


哦,我回答太慢了哈哈,我看到已经有另一个使用async/await的了。 - Dennis O'Keeffe

0

您可以将RXJS添加到您的项目中,并使用Observables.forkJoin()

使用Observables的解决方案(假设trainInput.mv()返回Observable):

/* Without a selector */
var source = Rx.Observable.forkJoin(
  trainInput.mv(`inputs/${req.body.caseName}/train_input.csv`),
  trainInput.mv(`inputs/${req.body.caseName}/train_output.csv`),
  trainInput.mv(`inputs/${req.body.caseName}/test_input.csv`)
);

var subscription = source.subscribe(
  function (x) {
    // On success callback
    console.log('Success: %s', x);
  },
  function (err) {
    // Error callback
    console.log('Error');
  },
  function () {
    // Completed - runs always
    console.log('Completed');
  });

// => Success: [result_1, result_2, result_3] or Error
// => Completed

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