如何在 Node.js 和 Express.js 中改进此代码以避免回调地狱

How to improve this code in Node.js and Express.js avoiding callback hell

我的一个控制器中有一个方法。控制器的目的是使用 webshot package.

打印一组 url

这是有问题的代码:

router.post('/capture', function (req, res, next) {

  //Check params remove 

  var json = JSON.parse(req.body.data);

  var promise = new Promise(function (resolve, reject) {

    var totalImages = Object.keys(json).length;
    var arrayListUrlImages = new Array(totalImages);
    var counter = 0;           
    var completeDir = dir + ''; //Directory URL    

    for (var value of json) {    
      var url = 'http://example.com/' + id + '/' + value.anotherValue;
      var folder = completeDir + id + '/' + value.anotherValue + '.jpg';

      //Options for capturing image
      var options = {
        renderDelay: 1000,
        quality: 100,
        phantomConfig:
        {
          'local-to-remote-url-access': 'true',
          'ignore-ssl-errors': 'true'
        }       
      };

      var anotherValue = value.anotherValue;

      (function (anotherValue) {

          webshot(url, folder, options, function (err) {
        // screenshot now saved            

        if (err === null) {

          var urlImage = "http://example.com/images/" + id + "/" + anotherValue + ".jpg";
          arrayListUrlImages.push(urlImage);
          counter++;
          console.log("Counter: " + counter);

          if (counter === totalImages) {                
            resolve(arrayListUrlImages);
          }
        }
        else {
          reject(err);
        }
      });    
      })(anotherValue);


    }




  }).then(function (arrayImages) {

    res.send(arrayImages);   


  }).catch(function (errorVale) {
    res.send(null);


     });
});

这段代码没有问题...但我想做得更好。我不知道有多少 URL 需要检查(这是重要的细节,因为我需要为每个或类似的 做一个检查)。

我读过关于 async package... 是否更好的选择是将此代码移动到类似 async.parallel 的位置?我可以在我的代码中使用 yield 吗?

谢谢!

既然你用的是Promise,我推荐Promise.all

It returns a promise that resolves when all of the promises in the iterable argument have resolved, or rejects with the reason of the first passed promise that rejects.

看来它解决了你的问题。

示例:

downloadOne = url => new Promise(resolve => {
   webshot(url, ....., (err, res) => resolve(res));
})

router.post('/capture', function (req, res, next) {
    var urls = JSON.parse(req.body.data);
    Promise.all(urls.map(downloadOne)).then(req.send);
}

老实说,您的代码看起来不错。

如果您不打算在此处添加更多逻辑,请保持原样。

可以做得更好的是将其迁移到 ES6 语法并提取另一个值函数,但我不知道这是否适用于您的情况。

对于这样简单的示例,您不需要使用 async。使用原生承诺:

router.post('/capture', function (req, res, next) {

    //Check params remove 

    const json = JSON.parse(req.body.data);

    Promise.all(Object.getOwnPropertyNames(json).map((key) => {
        var value = json[key];

        var url = 'http://example.com/' + id + '/' + value.anotherValue;
        var folder = completeDir + id + '/' + value.anotherValue + '.jpg';

        //Options for capturing image
        var options = {
            renderDelay: 1000,
            quality: 100,
            phantomConfig:
            {
                'local-to-remote-url-access': 'true',
                'ignore-ssl-errors': 'true'
            }       
        };

        return new Promise((resolve, reject) => {
            webshot(url, folder, options, function (err) {
                if (err) {
                    reject(err);
                    return;
                }

                var urlImage = "http://example.com/images/" + id + "/" + anotherValue + ".jpg";
                resolve(urlImage);
            }
        });
    }))
    .then((listOfUrls) => {
        res.json(listOfUrls); // List of URLs
    }, (error) => {
        console.error(error);
        res.json(null);
    });
});

这是一个基于内部函数的代码流示例:

router.post('/capture', function (req, res, next) {
    // Definitions

    // Load image
    function loadImage(value) {
        var url = 'http://example.com/' + id + '/' + value.anotherValue;
        var folder = completeDir + id + '/' + value.anotherValue + '.jpg';

        //Options for capturing image
        var options = {
            renderDelay: 1000,
            quality: 100,
            phantomConfig:
            {
                'local-to-remote-url-access': 'true',
                'ignore-ssl-errors': 'true'
            }       
        };

        return webshotPromise(url, folder, options);
    }

    // Load whebshot as a promise
    function webshotPromise(url, folder, options) {
        return new Promise((resolve, reject) => {
            webshot(url, folder, options, function (err) {
                if (err) {
                    reject(err);
                }

                var urlImage = "http://example.com/images/" + id + "/" + anotherValue + ".jpg";
                resolve(urlImage);
            }
        });
    }

    // The method flow
    const json = JSON.parse(req.body.data);

    // Get json keys and iterate over it to load
    Promise.all(
        Object.getOwnPropertyNames(json).map(key => loadImage(json[key]))
    )
    // Got list of urls
    .then((list) => {
        res.json(list); 
    }, (error) => {
        console.error(error);
        res.json(null);
    });
});