0

I have a function that loops through in indeterminate number of items and does an asynchronous call on each one to get additional data (the content of html template files). The callback does some checking. The resulting function should be thenable. $q is injected earlier, this code is part of a factory.

function searchHelpTopics(topics, searchPhrase) {
    if (topics == null || topics.length == 0) return "No search results";
    var results = [];
    var promises = [];
    for (var i = 0; i < topics.length; i++) {
        var templateURL = topics[i].URL;
        var topic = topics[i];
        if (topics[i].HelpTopicId != "Search") {
            var promise = $templateRequest(templateURL).then(function (template) {
                var text = HTMLToText(template, true);
                // do the search
                if (text.indexOf(searchPhrase) > -1) {
                    if (text.length > 50) text = text.substring(0, 50);
                    var result = {};
                    result.title = topic.Title;
                    result.excerpt = text;
                    result.helpID = topic.HelpTopicID;
                    results.push(result);
                }
            });
            promises.push(promise);
        }
    }
    return $q.all(promises).then(function () {
        return results;
    })

The problem here is that the for loop does not wait for the callbacks obviously and so the topic being used by the callback is not the correct one. I need a way to pass topic into the callback on each loop.

1

2 Answers 2

1

Because JS has only function scope you can rewrite your code to use function instead of 'for' loop (which is usually better).

To do that you can use JS built-in forEach (which is available starting from version 1.6 so almost for all browsers) or good functional style libraries like underscore.js or lodash.js.

Or even better - to use Array.map and Array.filter - see the code

function processTemplate(topic, template) {
  var text = HTMLToText(template, true);
  // do the search
  if (text.indexOf(searchPhrase) < 0) {
    return;
  }
  if (text.length > 50) {
    text = text.substring(0, 50);
  }
  return {
    title: topic.Title,
    excerpt: text,
    helpID: topic.HelpTopicID
  };
}

function searchHelpTopics(topics, searchPhrase) {
  if (!topics || topics.length === 0) {
    return "No search results";
  }
  var promises = topics
    .filter(function(topic) { return topic.HelpTopicId !== "Search"; })
    .map(function(topic) {
      return $templateRequest(topic.URL).then(processTemplate);
    });
  return $q.all(promises)
    .then(function (results) {
      return results.filter(function (result) {
        return result; // filters out 'undefined'
      });
    });
}

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks I like the approach but it doesn't work because neither topic nor searchPhrase get passed into processTemplate. I'll try using forEach next...
I was able to get the above code to run by replacing return $templateRequest(topic.URL).then(processTemplate); with return $templateRequest(topic.URL).then(function (template) { processTemplate(template, topic, searchPhrase); }); but the result is an empty array when it should have 3 results
Got it working. I've added an answer with the working code.
0

The is not a complete solution but enough to indicate how it works

somefactory.getHelpTopics().then(function (topics) {
    somefactory.searchHelpTopics(topics, searchText).then(function (searchResults) {
        vm.searchResults = searchResults;
        vm.helpID = "Search";
    });
});

--- some factory functions ----
function searchHelpTopics(topics, searchPhrase) {
    if (!topics || topics.length === 0) return "No search results";
    var promises = topics
        .filter(function (topic) { return topic.HelpTopicId !== "Search"; })
        .map(function (topic) {
            return $templateRequest(topic.URL).then(function (template) {
                return searchHelpTemplate(template, topic, searchPhrase);
            });
        });
    return $q.all(promises).then(function (results) {
        return results.filter(function (result) {
            return result; // filters out 'undefined'
        });
    });
}
function searchHelpTemplate(template, topic, searchPhrase) {
    var text = HTMLToText(template, true);
    // do the search
    if (text.indexOf(searchPhrase) < 0 && topic.Title.indexOf(searchPhrase) < 0) {
        return;
    }
    if (text.length > 50) {
        text = text.substring(0, 50);
    }
    return {
        title: topic.Title,
        excerpt: text,
        helpID: topic.HelpTopicId
    };
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.