【问题标题】:Angular promises in a map loop地图循环中的角度承诺
【发布时间】:2015-11-16 21:23:54
【问题描述】:

我遇到了一个奇怪的问题,在 map 循环中调用了一些 Promise。

我已将 Angular 服务编码为与远程 API 的接口。 API 提供了一些获取调查数据的方法。

angular.module('viewerServices').factory('SurveyService', [

    '$q',
    '$http',
    'Debugger',
    'CONFIG',

    function ($q, $http, Debugger, CONFIG) {

        var data;
        /* survey data object structure
        {
            name: null,
            questions: [
                {
                    type: null,
                    text: null,
                    answers: [
                        {
                            text: null
                        }
                    ]
                }
            ]
        }
        */

        var getQuestionAnswers = function (question) {
            var deferred = $q.defer();
            var parse = function (str) {
                var answers = [];
                var answer;
                // answers list is defined like this: reponsetext1;weight1;responsetext2;weight2;...
                var fragments = str.split(';');
                fragments.forEach(function (fragment, index) {
                    if (index % 2 === 0) {
                        answer = {};
                        answer.text = fragment;
                    }
                    else if (index % 2 !== 0) {
                        answer.weight = fragment;
                        answers.push(answer);
                    }
                });
                return answers;
            };
            if (CONFIG.ENV !== 'local') {
                com.veeva.clm.getDataForObject('Survey_Question_vod__c', question.id, 'Answer_Choice_vod__c', function (response) {
                    question.answers = parse(response.Survey_Question_vod__c.Answer_Choice_vod__c);
                    deferred.resolve(question);
                });
            }
            else {
                $http.get('survey/survey.json').then(function (response) {
                    question.answers = parse(response.data.Survey_Question_vod__c[question.id].Answer_Choice_vod__c);
                    deferred.resolve(question);
                });
            }
            return deferred.promise;
        };

        var getQuestionText = function (question) {
            var deferred = $q.defer();
            if (CONFIG.ENV !== 'local') {
                com.veeva.clm.getDataForObject('Survey_Question_vod__c', question.id, 'Text_vod__c', function (response) {
                    question.text = response.Survey_Question_vod__c.Text_vod__c;
                    deferred.resolve(question);
                });
            }
            else {
                $http.get('survey/survey.json').then(function (response) {
                    question.text = response.data.Survey_Question_vod__c[question.id].Text_vod__c;
                    deferred.resolve(question);
                });
            }
            return deferred.promise;
        };

        var getSurvey = function () {
            var deferred = $q.defer();
            if (CONFIG.ENV !== 'local') {
                com.veeva.clm.getDataForCurrentObject("Presentation", "Survey_vod__c", function (response) {
                    deferred.resolve({
                        id: response.Presentation.Survey_vod__c
                    });
                });
            }
            else {
                $http.get('survey/survey.json').then(function (response) {
                    deferred.resolve({
                        id: response.data.Presentation.Survey_vod__c
                    });
                });
            }
            return deferred.promise;
        };

        var getSurveyName = function (survey) {
            var deferred = $q.defer();
            if (CONFIG.ENV !== 'local') {
                com.veeva.clm.getDataForObject("Survey_vod__c", survey.id, "Name", function (response) {
                    survey.name = response.Survey_vod__c.Name;
                    deferred.resolve(survey);
                });
            }
            else {
                $http.get('survey/survey.json').then(function (response) {
                    survey.name = response.data.Survey_vod__c[survey.id].Name;
                    deferred.resolve(survey);
                });
            }
            return deferred.promise;
        };

        var getSurveyQuestions = function (survey) {
            var deferred = $q.defer();
            survey.questions = [];
            if (CONFIG.ENV !== 'local') {
                com.veeva.clm.getSurveyQuestions_Survey(survey.id, function (response) {
                    response.Survey_Question_vod__c.forEach(function (question) {
                        survey.questions.push({
                            id: question.ID
                        });
                    });
                    deferred.resolve(survey);

                });
            }
            else {
                $http.get('survey/survey.json').then(function (response) {
                    response.data.SurveyQuestions_Survey[survey.id].forEach(function (question) {
                        survey.questions.push({
                            id: question.ID
                        });
                    });
                    deferred.resolve(survey);
                });
            }
            return deferred.promise;
        };


        var that = {

            getData: function () {
                var deferred = $q.defer();
                if (!data) {
                    data = {};
                    getSurvey().then(function (survey) {
                        // get survey's name
                        return getSurveyName(survey);
                    }).then(function (survey) {
                        // get survey's questions
                        return getSurveyQuestions(survey);
                    }).then(function (survey) {
                        // loop through survey's questions
                        var promises = survey.questions.map(function (question) {
                            // get question text
                            return getQuestionText(question).then(
                                function (q) {
                                    // get question's possible answers
                                    return getQuestionAnswers(q).then(
                                        function (q) {
                                            Debugger.log(q);
                                        },
                                        function (error) {
                                            Debugger.log(error);
                                        }
                                    );
                                },
                                function (error) {
                                    Debugger.log(error);
                                }
                            );
                        });
                        $q.all(promises).then(
                            function () {
                                data = survey;
                                deferred.resolve(data);
                            },
                            function (error) {
                                Debugger.log(error);
                            }
                        );
                    });
                }
                return deferred.promise;
            }

        };

        return that;
    }

]);

在服务中检索数据时,指令会显示带有拖放界面的调查。

angular.module('viewerDirectives').directive('slide', [

    '$timeout',
    'FlowService',
    'SurveyService',
    'Debugger',

    function ($timeout, FlowService, SurveyService, Debugger) {
        return {
            restrict: 'A',
            link: function ($scope, $element) {

                // PRIVATE PROPERTIES
                var answers = {};
                var $draggables;
                var $dropzones;


                // SCOPE PROPERTIES
                $scope.isValid = false;


                SurveyService.getData().then(function (data) {
                    $scope.questions = data.questions;

                    $timeout(function () {
                        $draggables = $element.find('.drag');
                        $dropzones = $element.find('.dropzone');
                        $dropzones.each(function () {
                            this.x =  $(this).offset().left;
                            this.y =  $(this).offset().top;
                        });

                        $draggables.each(function () {
                            $(this).on('mousedown mouseup', function (evt) {
                                evt.stopPropagation();
                            });
                            this.initX = $(this).offset().left;
                            this.initY = $(this).offset().top;
                        });

                        Draggable.create($draggables, {
                            type: 'x,y',
                            bounds: '.slide',
                            onPress: function () {
                                this.startX = this.x;
                                this.startY = this.y;
                            },
                            onDragStart: function () {
                                $(this.target).addClass('is-moving');
                            },
                            onDragEnd: function () {
                                var draggable = this;
                                var $parent = $(draggable.target).parent();
                                $(draggable.target).removeClass('is-moving');
                                draggable.dropped = false;
                                $dropzones.each(function (dropzoneIndex, dropzone) {
                                    if (draggable.hitTest(this, '50%') && !isOccupiedDropzone(dropzoneIndex)) {
                                        draggable.dropped = true;
                                        answers[draggable.target.textContent] = dropzoneIndex;
                                        TweenLite.to(draggable.target, 0.2, { ease: Expo.easeOut, x: this.x - draggable.target.initX, y: this.y - draggable.target.initY });
                                        //
                                        $scope.isValid = isValid();
                                        $scope.$apply();
                                        return false;
                                    }
                                });
                                if (draggable.hitTest($parent, '50%')) {
                                    answers[draggable.target.textContent] = null;
                                    TweenLite.to(draggable.target, 0.2, { ease: Expo.easeOut, x: 0, y: 0 });
                                    $scope.isValid = isValid();
                                    $scope.$apply();
                                    return;
                                }
                                if (!draggable.dropped) {
                                    TweenLite.to(draggable.target, 0.5, { ease: Expo.easeOut, x: draggable.startX, y: draggable.startY });
                                }
                            }
                        });
                    }, 0);
                });


                // PRIVATE METHODS
                var isOccupiedDropzone = function (index) {
                    for (var draggable in answers) {
                        if (answers[draggable] === index) {
                            return true;
                        }
                    }
                    return false;
                };

                var isValid = function () {
                    var valid = Object.keys(answers).length === $dropzones.length;
                    for (var draggable in answers) {
                        valid = valid && (answers[draggable] !== null);
                    }
                    return valid;
                };


                // SCOPE METHODS
                $scope.submit = function () {
                    // TODO
                };

            }
        };
    }
]);

“本地”模式允许我在本地服务器上模拟 API 调用。在这种模式下,一切正常,全局 $q.all() 承诺确实解析了预期的数据,并且指令显示了预期的尽可能多的可拖动元素。

但是当我打开“prod”模式时,全局$q.all() 承诺永远不会被解决,因为只有map 循环中的最后一个承诺链被解决。就像我在循环中遇到了异步进程的基本问题,但是map 循环不会阻止这一点吗?无论如何,我还测试了一个简单的for 循环和一个闭包,结果相同。

欢迎任何帮助,因为我快要发疯了!

PS:我是法国人,所以请放纵我的英语!

【问题讨论】:

    标签: javascript angularjs dictionary promise


    【解决方案1】:

    您在getData() 中缺少return

    return $q.all(promises).then(...);
    

    更进一步,您可能还考虑缓存数据承诺而不是data,并进行大量整理,如下所示:

    var dataPromise;
    ...
    getData: function () {
        if (!dataPromise) {
            function processQuestion(question) {
                return getQuestionText(question)
                .then(getQuestionAnswers)
                .then(Debugger.log, Debugger.log);
            }
            dataPromise = getSurvey()
            .then(getSurveyName)
            .then(getSurveyQuestions)
            .then(function (survey) {
                return $q.all(survey.questions.map(processQuestion)).then(function () {
                    return survey;
                }, Debugger.log );
            });
        }
        return dataPromise;
    }
    

    编辑:

    尝试像这样编写所有 getter 函数:

    var getSurveyName = function (survey) {
        if (CONFIG.ENV !== 'local') {
            var deferred = $q.defer();
            com.veeva.clm.getDataForObject("Survey_vod__c", survey.id, "Name", function (response) {
                survey.name = response.Survey_vod__c.Name;
                deferred.resolve(survey);
            });
            return deferred.promise;
        } else {
            return $http.get('survey/survey.json').then(function (response) {
                survey.name = response.data.Survey_vod__c[survey.id].Name;
                return survey;
            });
        }
    };
    

    【讨论】:

    • 酷重构,谢谢。但问题出在其他地方,我怀疑 API 不支持异步调用,导致 map 循环出现问题,因为每个 2 个 Promise 链都是异步调用的。我必须找到一种从循环中创建 n 承诺链的方法,以便同步处理它们。
    • 我不完全理解,因为缺少return 是两种模式的共同点,如果没有它,两种模式都不会起作用。我也不明白“那个 API 不支持异步调用”,因为$http() 肯定会返回一个承诺。我唯一能建议的就是你 return $http() 来自每个 getter 函数。这比解决您自己的$q.defer() 更好,因为它会导致任何$http() 错误被记录回getData()。请参阅上面的编辑。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-08
    • 2016-02-19
    • 2018-03-25
    • 2015-11-22
    • 2021-12-15
    相关资源
    最近更新 更多