JavaScript 如果已经使用了 promise,我该如何克服 JavaScript 的异步性?
JavaScript How do I overcome asynchronousness of JavaScript if a promise have already been used?
这不是我第一次遇到这个问题,但我总能找到解决方法,我想这很好,但现在我想了解如何改进。因此,在我的示例中,我有一个课程列表,我需要计算每个课程的平均评分:
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 列表之前触发,函数总是 returns 空.
我的解决方法如下:
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 语句,而且我也不能在承诺上使用承诺。如果你处在我的位置,你会如何解决这个问题?
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));
两者都将引用同一个承诺,因此它们将同时被解析并得到相同的结果。没有两个请求,只有一个。
如果你正在使用 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;
};
vm.getAverageRating()
依赖于异步操作的结果,因此它对同步时间线 return 没有任何影响。你所做的基本上是正确的,但如果 vm.getAverageRating()
return 是一个承诺会更好。所以我会模仿你的代码如下;
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));
回答问题
How would you solve this problem if you were in my place?
你的两种方法的组合是
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
});
the first block of code is simply, in case ES2015+ is unfamiliar
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;
});
};
使用 promise 没问题,但你应该
- return 对值的承诺,而不是写入某个预先确定的对象
- 简化您的代码,特别是变量声明
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;
});
这不是我第一次遇到这个问题,但我总能找到解决方法,我想这很好,但现在我想了解如何改进。因此,在我的示例中,我有一个课程列表,我需要计算每个课程的平均评分:
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 列表之前触发,函数总是 returns 空.
我的解决方法如下:
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 语句,而且我也不能在承诺上使用承诺。如果你处在我的位置,你会如何解决这个问题?
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));
两者都将引用同一个承诺,因此它们将同时被解析并得到相同的结果。没有两个请求,只有一个。
如果你正在使用 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;
};
vm.getAverageRating()
依赖于异步操作的结果,因此它对同步时间线 return 没有任何影响。你所做的基本上是正确的,但如果 vm.getAverageRating()
return 是一个承诺会更好。所以我会模仿你的代码如下;
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));
回答问题
How would you solve this problem if you were in my place?
你的两种方法的组合是
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
});
the first block of code is simply, in case ES2015+ is unfamiliar
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;
});
};
使用 promise 没问题,但你应该
- return 对值的承诺,而不是写入某个预先确定的对象
- 简化您的代码,特别是变量声明
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;
});