Meteor.js:如何延迟 Meteor.call() 直到拥有所有数据?这是范围问题还是异步问题?

Meteor.js: how to delay Meteor.call() until have all data? Is this a scoping issue or an async issue?

我有一个正在构建的 Meteor 应用程序,它是一个 CMS。这个想法是一个网页可以有很多行,每一行都有很多文章。文章有图像、header 和段落。

为了保存图像,我使用了 edgee:slingshot 包。由于 sendFile($img, articlesArray); 是异步的,因此 sendFile($img, articlesArray); 之前 Meteor.call() 运行 的代码完成。这会导致网页保存在 mongodb 中,而没有保存任何行数组。

我能够在数据库中创建没有行(及其文章)的网页,即使 console.log 显示网页设置正确(包含行和文章 - 请参阅底部屏幕截图)。就好像 webPageAttributes 没有传递给 Meteor.call()。如果这是范围界定问题或异步问题,我会不知所措。

这里是生成的HTML:

这是 运行 表单提交中的 client-side 代码:

'submit form': function(e, template) {
    e.preventDefault();

    var articlesArray = [];
    var rowDivsArray = template.findAll('.row');

    _.map(rowDivsArray, function(div) {
        if (div.getAttribute("id") != null) {
            articlesArray.push(div);
        }
    });

    var articles = _.map(articlesArray, function(rowDiv) {
        var articlesArray = [];

         _.each(rowDiv, function() {
            var $rowDiv = $(rowDiv);
            var $articles = $rowDiv.find('div.article-container');

            $.each($articles, function(index, value) {
                var $img = $(value).find('input:file')[0].files[0];
                // console.log("$img: ", $img);
                var $input = $(value).find('input.form-control');
                var $text = $(value).find('textarea');
                var $style = $(value).find('input:radio:checked');

                var createArticle = function(downloadUrl){
                    console.log("createArticle() init");
                    var $article = {
                        img: downloadUrl,
                        header: $input.val(),
                        paragraph: $text.val(),
                        style: $style.val()
                    };
                    return $article;
                };

                var sendFile = function(img, articlesArray){
                    var articlesArray = articlesArray;
                    var upload = new Slingshot.Upload("articleImgUpload");
                    upload.send(img, function (error, downloadUrl) {
                        if (error) {
                            console.log("ERROR in upload: ", error);
                        } else {
                            createTheArticleAndAddToArray(downloadUrl);
                            return;
                        }
                    });
                    return upload;
                };

                // this is async, I need this to execute,
                sendFile($img, articlesArray);

                var createTheArticleAndAddToArray = function(downloadUrl) {
                    console.log("createTheArticleAndAddToArray() articlesArray before push: ", articlesArray);
                    var $article = createArticle(downloadUrl);
                    articlesArray.push($article);

                    console.log("createTheArticleAndAddToArray() articlesArray after push: ", articlesArray);
                    return articlesArray;
                };
            });
        });
        return articlesArray;
    });

    var makeWebPage = function(articles) {
        var webPage = {
            title: $(e.target).find('[name=title]').val(),
            rows: articles
        };
        return webPage;
    };

    var webPage = makeWebPage(articles);

    Meteor.call('newWebPageInsert', webPage, function(error, result) {
        if (error) {
            return alert(error.reason);
        } else {
            Router.go('/new-web-page');
            console.log("result from Meteor.call: ", result);
        }
    });
}

这里是 server-side 将网页插入数据库的代码:

    // this will create the webPage with all its attributes, but no rows array and no articles
        Meteor.methods({
            newWebPageInsert: function(webPageAttributes) {  
                console.log("webPageAttributes argument in newWebPageInsert: ", webPageAttributes);         
                check(Meteor.userId(), String);
                check(webPageAttributes, {
                    title: String,
                    rows: Array
                });
                var user = Meteor.user();
                var webPage = _.extend(webPageAttributes, {
                    userId: user._id,
                    submitted: new Date()
                });
                var webPageId = PublicWebPages.insert(webPage);
                return {
                    _id: webPageId
                };

            }

目标是一个网页,有一个行数组,行数组有 sub-arrays 表示页面的行。 sub-array 包含文章 objects。

webPage: {
  title: "Page Title",
  rows: [
     [{img: "path to AWS", header: "article header", paragraph: "article paragraphs"}, {img: "path to AWS", header: "article header", paragraph: "article paragraphs"}], // 1st row 
     [{img: "path to AWS", header: "article header", paragraph: "article paragraphs"}, {...}]  // 2nd row
  ]
}

如果我将 Meteor.call() 移动到 upload.send() 的 else 子句,那么将为每篇文章保存一个具有所有文章属性的网页。如果我保留如图所示的代码,则会创建网页,但没有任何行或文章。如此混乱,因为 console.log 显示网页已正确设置文章和表单。

我是否暂停 Meteor.call() 等待?同样,webPage.rows 似乎没有正确传递给 newWebPageInsert。如何将 webPage.rows 传递给 Meteor.methods() 中的 newWebPageInsert()?这是应该遵循的正确逻辑还是应该在其他地方暂停一下?

更新 1: 看起来 Meteor.call() 在创建行和文章之前 运行,这就是它们没有被保存的原因。

证明console.log:

所以这似乎是一个异步问题....如何暂停 Meteor.call() 直到文章创建?

这是 objects 的 console.log 及其时间:

更新 2: 时间和范围随着代码的变化而保留:

'submit form': function(e, template) {
    e.preventDefault();

    var articlesArray = [];
    var rowDivsArray = template.findAll('.row');
    // console.log("rowDivsArray: ", rowDivsArray);

    _.map(rowDivsArray, function(div) {
        if (div.getAttribute("id") != null) {
            articlesArray.push(div);
        }
    });

    // console.log("articlesArray: ", articlesArray);

    var articles = _.map(articlesArray, function(rowDiv) {
        // console.log("rowDiv: ", rowDiv); // get row's articles
        var articlesArray = []; // {img: "https://childrens-center.s3.amazonaws.com/undefined/full-style.png", header: "hell yeah", paragraph:"test 1"}

         _.each(rowDiv, function() {
            var $rowDiv = $(rowDiv);
            var $articles = $rowDiv.find('div.article-container');

            $.each($articles, function(index, value) {
                var $img = $(value).find('input:file')[0].files[0];
                // console.log("$img: ", $img);
                var $input = $(value).find('input.form-control');
                var $text = $(value).find('textarea');
                var $style = $(value).find('input:radio:checked');

                var createArticle = function(downloadUrl){
                    console.log("createArticle() init");
                    var $article = {
                        img: downloadUrl,
                        header: $input.val(),
                        paragraph: $text.val(),
                        style: $style.val()
                    };
                    return $article;
                };

                var sendFile = function(img, articlesArray){
                    console.log("articlesArray passed to sendFile line 250: ", articlesArray);
                    var articlesArray = articlesArray;
                    var upload = new Slingshot.Upload("articleImgUpload");
                    upload.send(img, function (error, downloadUrl) {
                        if (error) {
                            console.log("ERROR in upload: ", error);
                        } else {
                            // console.log("the articles array in sendfile before push line 257: ", articlesArray);

                            return createTheArticleAndAddToArray(downloadUrl);  
                        }
                    });
                    return upload;
                };

                // this is async, I need this to execute,
                sendFile($img, articlesArray);

                // will need to create the $article and push into db
                var createTheArticleAndAddToArray = function(downloadUrl) {
                    console.log("createTheArticleAndAddToArray() articlesArray before push: ", articlesArray);
                    var $article = createArticle(downloadUrl);
                    articlesArray.push($article);

                    console.log("createTheArticleAndAddToArray() articlesArray after push: ", articlesArray);
                    var articles = articlesArray;
                    var makeWebPage = function(articles) {
                        // console.log("articles within makeWebPage(): ", articles);
                        var webPage = {
                            title: $(e.target).find('[name=title]').val(),
                            rows: articles
                        };

                        Meteor.call('newWebPageInsert', webPage, function(error, result) {
                            console.log("webPage in the Meteor.call line 300: ", webPage);
                            console.log("webPage.rows in the Meteor.call line 300: ", webPage.rows);
                            if (error) {
                                return alert(error.reason);
                            } else {
                                Router.go('/new-web-page');
                                console.log("result from Meteor.call: ", result);
                            }
                        });
                        return webPage;
                    };

                    console.log("articles to insert line 294: ", articles);
                    var webPage = makeWebPage(articles);
                    return articlesArray;
                };
            });
        });

        return articlesArray;
    });
}

现在只需要在收集完所有文章后控制循环停止,然后调用一次makeNewWebPage()

试试这个。

 Meteor.subscribe('whateverDataYouAreWaiting', Session.get('whateverDataYouAreWaiting'), {
              onError: function(){
                console.log('Error');
              },
              onReady: function() {
              //Here trigger a Session to flag the documents are ready, or call        Meteor.call
              }
            });

我最终进行了重构,这样文章就不会嵌入到网页中,因此每篇文章都会对 Amazon Storage 进行单独的 post。它仍然需要更多的工作,例如错误处理,但核心功能在那里。

Template.addWebPageArticles.events({
    'submit form': function(e, template) {
        e.preventDefault();     
        var rowsArray = template.findAll('.row');

        var allRowsWithNumberAssignedArray = [];
        _.each(rowsArray, function(row) {
            Session.set('rowNumber', (Session.get('rowNumber') + 1));
            $(row).attr( "id", "row" + Session.get('rowNumber') );
            allRowsWithNumberAssignedArray.push(row);
        });

        var rowsWithNumberAssignedThatContainArticlesArray = _.rest(allRowsWithNumberAssignedArray, 1);

        _.each(rowsWithNumberAssignedThatContainArticlesArray, function(row) {
            var articlesInRowInstanceArray = $(row).find('.article-container');

            _.each(articlesInRowInstanceArray, function(articleContainerDiv) {
                var article = {
                    page_id: template.data._id,
                    row_number: $(articleContainerDiv).parent().attr('id'),
                    place_in_row: String( _.indexOf(articlesInRowInstanceArray, articleContainerDiv) ),
                    image_path: $(articleContainerDiv).find('input:file')[0].files[0],
                    header: $(articleContainerDiv).find('input.form-control').val(),
                    paragraph: $(articleContainerDiv).find('textarea').val(),
                    style: $(articleContainerDiv).find('input:radio:checked').val()
                }
                var imgToUpload = function(article) {
                    var image = article.image_path;
                    return image;
                }

                var image = imgToUpload(article);

                var sendFile = function(image){
                    var upload = new Slingshot.Upload("articleImgUpload");
                    upload.send(image, function (error, downloadUrl) {
                        if (error) {
                            console.log("ERROR in upload: ", error);
                        } else {

                            var updateArticle = function(downloadUrl) {
                                article.image_path = downloadUrl;
                                return article;
                            }
                            var articleReturned = updateArticle(downloadUrl);

                            Meteor.call('newWebPageArticleInsert', articleReturned, function(error, result) {
                                if (error) {
                                    return alert(error.reason);
                                } else {
                                    Router.go('/new-web-page');
                                }
                            });
                        }
                    });
                    return upload;
                };

                sendFile(image);
            });
        });
    }
});