【问题标题】:JavaScript How do I overcome asynchronousness of JavaScript if a promise have already been used?JavaScript 如果已经使用了 Promise,我如何克服 JavaScript 的异步性?
【发布时间】:2018-02-05 19:24:07
【问题描述】:

这不是我第一次遇到这个问题,但我总是设法找到解决方法,我想这很好,但现在我想了解如何改进。因此,在我的示例中,我有一个课程列表,我需要计算每个课程的平均评分:

var vm = this;

course.averageRating = vm.getAverageRating(course.id);

现在,什么不起作用:

vm.getAverageRating = function (courseId) {
       let sum = 0, courses = [];
       courseContext.getUserCourseFeedback(courseId).then(function(results){
            courses = results;
        });

       courses.forEach(function (course) {
            if(course && course.feedback_rating) {
                sum += course.feedback_rating;
            }
       });
       return courses.length > 0 ? sum / courses.length : null;
};

显然,courses.forEach 在应用程序实际从服务器下载 userCourses 列表之前触发,并且函数总是返回 null

我的解决方法如下:

vm.averageRatings = {};

vm.getAverageRating = function (courseId) {
  let sum = 0, courses = [];
  courseContext.getUserCourseFeedback(courseId).then(function(results){
       courses = results;

       courses.forEach(function (course) {
            if(course && course.feedback_rating) {
                sum += course.feedback_rating;
            }
       });

       vm.averageRatings[courseId] = courses.length > 0 ? sum/courses.length : null;
  });
};

我相信这不是解决这个问题的最佳方法。在我的第一个示例中,我不能在 then 中使用 return 语句,我也不能在 promise 上使用 promise。如果你在我的位置,你会如何解决这个问题?

【问题讨论】:

  • “我不能在承诺上使用承诺”...?好吧,你可以;但你真的只需要return你已经拥有的承诺。
  • 我认为您提出的“解决方法”实际上是(其中一种)正确的方法。不是最优雅的,但正确的。
  • 同样的问题,我想要更干净的代码结果

标签: javascript angularjs asynchronous promise angular-promise


【解决方案1】:

使用承诺很好,但你应该

  • 返回值的承诺,而不是写入某个预先确定的对象
  • 简化您的代码,特别是变量声明

function getAverageRating(courseId) {
  return courseContext.getUserCourseFeedback(courseId).then(function(courses) {
//^^^^^^                                                ^^^^^^^
    let sum = 0;
    for (const course of courses)
      sum += course && course.feedback_rating || 0;
    return courses.length > 0 ? sum/courses.length : null;
  });
};

getAverageRating(course.id).then(avg => {
  course.averageRating = avg;
});

【讨论】:

  • 谢谢!这是最好的解决方案。虽然 return courseContext.getUserCourseFeedback.... 返回一个对象而不是一个数字(类似于 d.$$state.value 来获取平均值)。除了通过对象中的几层获取值之外,您知道是否有其他方法可以解决它?
  • 它返回一个 promise,你必须使用 .then() 等待它。不要窥视它的私有属性。
【解决方案2】:

getAverageRating 异步的。但是您可能想要缓存结果:

 var cache = {};   
 vm.getAverageRating = function (courseId) {

  return cache[courseId] || 
   (cache[courseId] = courseContext.getUserCourseFeedback(courseId)
     .then(function(courses){
        return (
         courses.reduce((sum,course)=>sum + (course.feedback_rating || 0),0)
         / courses.length) || null;
    })
  );      
};

可以这样使用:

vm.getAverageRating(1).then( res => console.log(res));
vm.getAverageRating(1).then( res => console.log(res));

两者都将引用同一个promise,因此它们将同时被解析并得到相同的结果。没有两个请求,只有一个。

【讨论】:

    【解决方案3】:

    回答问题

    如果你在我的位置,你会如何解决这个问题?

    您的两种方法的组合将是

    vm.getAverageRating = function (courseId) {
     return courseContext.getUserCourseFeedback(courseId).then(courses => courses.length > 0 ? (courses.map(course => (course && course.feedback_rating) ? course.feedback_rating : 0).reduce((a, b) => a + b))/courses.length : null);
    };
    

    使用方法:

    vm.getAverageRating("someID").then(result => {
        // result is the result you want
    });
    

    第一块代码很简单,以防对ES2015+不熟悉

    vm.getAverageRating = function (courseId) {
        return courseContext.getUserCourseFeedback(courseId).then(function (courses) {
            return courses.length > 0 ? courses.map(function (course) {
                return (course && course.feedback_rating) ? course.feedback_rating : 0;
            }).reduce(function (a, b) {
                return a + b;
            }) / courses.length : null;
        });
    };
    

    【讨论】:

    • 你错过了回报:/
    • 是的。我以为我添加了它。这是我发表评论的原因
    【解决方案4】:

    如果你使用的是 babel 或 Node 8.x,你可以使用async / await

    这会将您的功能简化为如下所示:

    vm.getAverageRating = async function (courseId) {
           let sum = 0, courses = [];
           await courseContext.getUserCourseFeedback(courseId).then(function(results){
                courses = results;
            });
    
           courses.forEach(function (course) {
                if(course && course.feedback_rating) {
                    sum += course.feedback_rating;
                }
           });
           return courses.length > 0 ? sum / courses.length : null;
    };
    

    【讨论】:

    • 这并不是真正的简化。
    【解决方案5】:

    vm.getAverageRating() 依赖于异步操作的结果,因此它不会向同步时间线返回任何内容。你所做的基本上是正确的,但如果 vm.getAverageRating() 返回一个承诺会更好。所以我会模仿你的代码如下;

    function getAverageRating(n){
      return Promise.resolve(Array.from({length: n})
                                  .map(_ => 25+Math.floor(76*Math.random()))) // get course ratings
                    .then(function(crs){
                            var sum = crs.reduce((p,c) => p+c),
                                avg = crs.length ? sum / crs.length : null;
                            return [crs,avg];
                          });
    }
    getAverageRating(10).then(([crs,avg]) => console.log("course ratings are: ", crs, "average is: ", avg));

    【讨论】:

      猜你喜欢
      • 2018-01-14
      • 1970-01-01
      • 1970-01-01
      • 2015-06-30
      • 2021-09-08
      • 2016-08-03
      • 2021-11-10
      • 1970-01-01
      • 2020-03-04
      相关资源
      最近更新 更多