承诺nodejs可读性改进

时间:2018-01-15 08:13:49

标签: javascript node.js promise

我写了一些返回承诺的函数,从google analytics api获取数据。我认为我所写的内容被称为回调地狱......

有人可以帮助我优化此代码(或提供提示/最佳做法),以便更好地阅读。

var express = require('express');
var router = express.Router();
var googleAuth = require('google-oauth-jwt');
var google = require('googleapis');
var app = express();

module.exports.getGoogleData = function (jwtClient,analytics,VIEW_ID){
  return new Promise(function(resolve, reject){
    return getOrdersToday(jwtClient,analytics,VIEW_ID).then(function (orders) {
      return getOnlineUsersToday(jwtClient,analytics,VIEW_ID).then(function (users) {
        return getSearchedToday(jwtClient, analytics, VIEW_ID).then(function (searched){
          return getPageviewsTodayAndUsersToday(jwtClient, analytics, VIEW_ID).then(function (pageviews){
            var returndata =[
              {
                "Orders":orders,
                "Onlineusers":users,
                "searched":searched,
                "pageviews":pageviews[0].pageviews,
                "usersToday":pageviews[0].users
              }
            ]
            resolve(returndata);
          });
        });
      });
    });
  });
}

示例getfunction

function getOrdersToday(jwtClient,analytics,view_id){
  return new Promise(function(resolve,reject){
    analytics.data.ga.get({
        'auth':jwtClient,
        'ids': view_id,
        'metrics': 'ga:totalEvents',
        'start-date': 'today',
        'end-date': 'today',
        filters: 'ga:eventAction==Bestelling geplaatst',
        'max-results': '1'
    }, function(err, response) {
          // handle the errors (if any)
          if(err){
            console.log(err)
            reject(err)
          } else
            console.log('Response:',response)
            resolve(response.totalsForAllResults["ga:totalEvents"]);
        });
    });
}

5 个答案:

答案 0 :(得分:5)

根本不需要new Promise,事实上,通过使用它,你可以保持开放状态,如果你的一个电话中发生错误,就永远无法解决。请记住,then会返回新的承诺。因此,您可以将所有这些链接在一起如果您希望它们按顺序运行:

module.exports.getGoogleData = function (jwtClient,analytics,VIEW_ID){
  var result = {};
  return getOrdersToday(jwtClient,analytics,VIEW_ID)
    .then(function (orders) {
        result.Orders = orders;
        return getOnlineUsersToday(jwtClient,analytics,VIEW_ID);
    })
    .then(function (users) {
      result.Onlineusers = users;
      return getSearchedToday(jwtClient, analytics, VIEW_ID);
    }).then(function (searched){
      result.searched = searched;
      return getPageviewsTodayAndUsersToday(jwtClient, analytics, VIEW_ID);
    }).then(function (pageviews){
      result.pageviews = pageviews[0].pageviews;
      result.usersToday = pageviews[0].users;
      return [result]; // (Seems a bit odd that it's wrapped in an array, but
                       // that's what the original code did...)
    });
}

,这些操作看起来彼此独立。如果确实如此,请与Promise.all

并行运行
module.exports.getGoogleData = function (jwtClient,analytics,VIEW_ID){
  return Promise.all([
    getOrdersToday(jwtClient,analytics,VIEW_ID),
    getOnlineUsersToday(jwtClient,analytics,VIEW_ID),
    getSearchedToday(jwtClient, analytics, VIEW_ID),
    getPageviewsTodayAndUsersToday(jwtClient, analytics, VIEW_ID)
  ]).then(results => {
    return [{
      Orders: results[0],
      Onlineusers: results[1],
      searched: results[2],
      pageviews: results[3][0].pageviews,
      usersToday: results[3][0].users
    }];
  });
}

答案 1 :(得分:1)

解决这个丑陋代码的唯一方法就是杀死var express = require('express'); var router = express.Router(); var googleAuth = require('google-oauth-jwt'); var google = require('googleapis'); var app = express(); module.exports.getGoogleData = foo async function foo (jwtClient,analytics,VIEW_ID){ var orders = await getOrdersToday(jwtClient,analytics,VIEW_ID) var users = await getOnlineUsersToday(jwtClient,analytics,VIEW_ID) var searched = await getSearchedToday(jwtClient, analytics, VIEW_ID) var pageviews = await getPageviewsTodayAndUsersToday(jwtClient, analytics, VIEW_ID) return [{ "Orders":orders, "Onlineusers":users, "searched":searched, "pageviews":pageviews[0].pageviews, "usersToday":pageviews[0].users }] } ,为此你必须使用:

  

ES2017 async / await syntax

所以,看看这个新代码

set rng = Worksheets("Q Matrix").Range(Cells(i, 5), Cells(i, 8))

答案 2 :(得分:0)

以下是正确的可读链接您的承诺的方式。通过扩展垂直来正确链接减少嵌套。此外,您不需要实例化新的Promise,因为所有的get方法都已经返回promises。

function (jwtClient,analytics,VIEW_ID) {
    var returndata =[{}];
    return getOrdersToday(jwtClient,analytics,VIEW_ID).then(function (orders) {
        returndata[0].Orders = orders;
        return getOnlineUsersToday(jwtClient,analytics,VIEW_ID);
    }).then(function(users) {
        returndata[0].Onlineusers= users;
        return getSearchedToday(jwtClient, analytics, VIEW_ID);
    }).then(function(searched) {
        returndata[0].searched = searched;
        return getPageviewsTodayAndUsersToday(jwtClient, analytics, VIEW_ID);
    }).then(function(pageviews) {
        returndata[0].pageviews = pageviews[0].pageviews;
        returndata[0].usersToday = pageviews[0].users;
        return returndata;
    });
}    

答案 3 :(得分:0)

因为您已经创建了承诺。您可以使用Promise.all()并按照您希望它们执行的顺序将Promise数组传递给它。

有关详细信息,请参阅此处 - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

或者,您也可以使用 async / await - https://javascript.info/async-await

答案 4 :(得分:-2)

正如其他人所说,这些调用并不依赖于彼此,这意味着您不需要对它们进行排序,而是可以同时执行它们。这可能是这样的:

module.exports.getGoogleData = function(jwtClient, analytics, VIEW_ID) {
    return Promise.all([
        getOrdersToday(jwtClient, analytics, VIEW_ID),
        getOnlineUsersToday(jwtClient, analytics, VIEW_ID),
        getSearchedToday(jwtClient, analytics, VIEW_ID),
        getPageviewsTodayAndUsersToday(jwtClient, analytics, VIEW_ID)
    ])
    .then(function(results) {
        return {
            "Orders": results[0],
            "Onlineusers": results[1],
            "searched": results[2],
            "pageviews": results[3][0].pageviews,
            "usersToday": results[3][0].users
        };
    });
}

但我认为你的问题非常好,我希望你对良好模式的兴趣不会因为这个特定情况需要不同的解决方案而结束。正如我已经注意到elsewhere,异步模式是一个复杂的问题域。

所以,让我们假装你需要进行4次调用,每次调用取决于前一个调用的结果,所以必须按顺序执行。我们可以访问这个虚构的API:

function fetchOne(null)   // returns opaque ONE
function fetchTwo(ONE)    // must feed it ONE, returns opaque TWO
function fetchThree(TWO)  // must feed it TWO, returns opaque THREE
function fetchFour(THREE) // must feed it THREE, returns opaque FOUR

此外,让我们假装在此过程结束时,您希望能够访问所有这四个返回值,以便您可以对所有这些值进行响应。毕竟,如果它不能适应我们在现实世界中看到的各种复杂工作流程,那么模式有什么用呢?

因此,我们的目标是充实这个功能的实现:

function doTheWork() {
    // DO STUFF somehow, finally...
    return {
        one: ONE,
        two: TWO,
        three: THREE,
        four: FOUR,
        checksum: ONE + TWO + THREE + FOUR
    };
}

天真直接的尝试可能如下所示:

function doTheWork() {
    return fetchOne()
    .then((ONE) => {
        return fetchTwo(ONE)
    })
    .then((TWO) => {
        return fetchThree(TWO)
    })
    .then((THREE) => {
        return fetchFour(THREE)
    })
    .then((FOUR) => {
        // all work is done, let's package up the results
        return {
            one: ONE // uh-oh...
        };
    })
}

这将执行,但问题是我们无法再访问最终处理程序中的早期值。

实际上只有两种方法:(1)声明具有将由所有回调共享的范围的变量,或(2)在某种结构中的处理程序之间传递所有数据。我认为解决方案1非常不优雅,我建议不要这样做,但让我们看看它可能是什么样子:

function doTheWork() {
    var A, B, C

    return fetchOne()
    .then((ONE) => {
        A = ONE // store ONE in A
        return fetchTwo(ONE)
    })
    // ...
    .then((FOUR) => {
        // all work is done, let's package up the results

        // at this point, A & B & C contain the results of previous calls
        // and are within scope here
        return {
            one: A,
            two: B,
            three: C,
            four: FOUR,
            checksum: A + B + C + FOUR
        };
    })
}

这会有效,但我认为这是不好的做法,我不喜欢它。这是不好的做法,因为这些变量现在是公共的"以有限的方式,将它们暴露给doTheWork内的所有其他内容。它们可能会受到此函数中任何位置的语句的破坏。除非它们是标量,否则它们将通过引用传递,这意味着与它们相关的任何突变错误都可能以某种奇怪的方式表现出来。

此外,这些"临时"变量现在在共享命名空间(doTheWork的范围)中竞争好名称。理想情况下,您应该创建尽可能少的变量,并且每个变量应该只在必要时生存。这可能会节省内存,但它肯定会保存您的"命名池。"命名事物是精神上的疲惫。我是认真的。每个名称​​都必须好 - 永远不要用slapdash方式命名变量。事物的名称通常是代码中关于正在发生的事情的唯一线索。如果其中一些名字是一次性垃圾,那么你的生活就会变得更加艰难。

所以,让我们看看解决方案2.此时,我想指出,当您可以使用ES6解构语法时,此方法效果最佳。你可以在没有它的情况下做到这一点,但它有点笨拙。

我们将构建一个数组,该数组将缓慢累积每个异步调用所获取的所有数据。最后,数组将如下所示:

[ ONE , TWO , THREE , FOUR ]

通过有效地链接promises,并将这个数组从一个处理程序传递到下一个处理程序,我们都可以轻松地避免在所有这些方法中的Doom 分享异步结果。见下文:

function doTheWork() {

    return fetchOne()
    .then((ONE) => {
        return fetchTwo(ONE)
        .then((TWO) => [ ONE , TWO ])
    })
    .then(([ ONE , TWO ]) => {
        return fetchThree(TWO)
        .then((THREE) => [ ONE , TWO , THREE ])
    })
    .then(([ ONE , TWO , THREE ]) => {
        return fetchFour(THREE)
        .then((FOUR) => [ ONE , TWO , THREE , FOUR ])
    })
    .then(([ ONE , TWO , THREE , FOUR ]) => {

        return {
            one: ONE,
            two: TWO,
            three: THREE,
            four: FOUR,
            checksum: ONE + TWO + THREE + FOUR
        }

    })
}

这就是整个事情,但让我们逐步完成第一部分:

return fetchOne()
.then((ONE) => {
    return fetchTwo(ONE)
    .then((TWO) => [ ONE , TWO ])
})

正常情况下,fetchOne会返回ONE - 它是我们无法控制的第三方API。正如您所料,我们使用ONE进行第二次调用,确保return其承诺。但最后一行是真正的魔力:

.then((TWO) => [ ONE , TWO ])

第二个API调用仍然只返回TWO,但我们只是单独返回TWO,而是返回一个包含ONETWO的数组。该值 - 数组 - 成为下一个.then处理程序的参数。

这是有效的,因为嵌套的promises会自动解包。这是一个显示工作的简单示例:

function getDogName() {
    return fetchCatName()
    .then((catName) => 'Fido')
}

getDogName()
.then((dogName) => {
    console.log(dogName);
})

// logs 'Fido'

这说明您可以将.then处理程序附加到嵌套的promise,而外部promises将返回这些处理程序的结果。使用fetch获取JSON时,这是一种非常常见的模式:

function makeApiCall() {
    fetch(api_url) // resolves with a Response object
    .then((response) => response.json()) // extracts just the JSON and returns that instead!
}

回到我们的代码,因为我们想避开金字塔,我们将这个处理程序放在一边(而不是像你原来那样嵌套下一个调用)。下一行看起来像这样:

.then(([ ONE , TWO ]) => {

这是ES6的解构工作。通过对数组参数进行解构,我们可以在元素进入函数时对其进行命名。如果我们这样做,我们不妨给他们一些我们最喜欢的名字,那些进入世界的名字:ONETWO

然后我们重复这个模式,使用TWO来调用fetchThree,确保返回它的承诺,但不是在添加一个捆绑新可用{.then的小THREE处理程序之前。 {1}}进入我们传递的包裹。

我希望这会有所帮助。这是我在AWS中处理一些复杂的分支工作流程的模式,对S3和Dynamo以及其他具有大量并行条件的平台进行调用,混合阻塞和非阻塞行为。

异步模式是一个特殊问题域。即使使用正确的技术,也很难找到表达行为的明确方法,尤其是在基础工作流程错综复杂的情况下。在探索如今常见的数据图时,通常会出现这种情况。

快乐的编码。