diff --git a/lib/amd/build/str.min.js b/lib/amd/build/str.min.js
index 30589a89ea9..e2d06e48584 100644
--- a/lib/amd/build/str.min.js
+++ b/lib/amd/build/str.min.js
@@ -1 +1 @@
-define(["jquery","core/ajax","core/localstorage"],function(a,b,c){return{get_string:function(a,b,c,d){var e=this.get_strings([{key:a,component:b,param:c,lang:d}]);return e.then(function(a){return a[0]})},get_strings:function(d){var e,f=a.Deferred(),g=[],h=0,i=!1;for(h=0;h<d.length;h++)if(e=d[h],"undefined"==typeof e.lang&&(e.lang=a("html").attr("lang").replace("-","_")),"undefined"==typeof M.str[e.component]||"undefined"==typeof M.str[e.component][e.key]){var j=c.get("core_str/"+e.key+"/"+e.component+"/"+e.lang);j?("undefined"==typeof M.str[e.component]&&(M.str[e.component]=[]),M.str[e.component][e.key]=j):i=!0}if(i){var k=[];for(h=0;h<d.length;h++)e=d[h],k.push({methodname:"core_get_string",args:{stringid:e.key,component:e.component,lang:e.lang,stringparams:[]}});var l=b.call(k,!0,!1);a.when.apply(null,l).done(function(){var a=0;for(a=0;a<arguments.length;a++)e=d[a],"undefined"==typeof M.str[e.component]&&(M.str[e.component]=[]),M.str[e.component][e.key]=arguments[a],c.set("core_str/"+e.key+"/"+e.component+"/"+e.lang,arguments[a]),g[a]=M.util.get_string(e.key,e.component,e.param).trim();f.resolve(g)}).fail(function(a){f.reject(a)})}else{for(h=0;h<d.length;h++)e=d[h],g[h]=M.util.get_string(e.key,e.component,e.param);f.resolve(g)}return f.promise()}}});
\ No newline at end of file
+define(["jquery","core/ajax","core/localstorage"],function(a,b,c){var d=[];return{get_string:function(a,b,c,d){var e=this.get_strings([{key:a,component:b,param:c,lang:d}]);return e.then(function(a){return a[0]})},get_strings:function(e){var f,g=a.Deferred(),h=[],i=0,j=!1;for(i=0;i<e.length;i++)if(f=e[i],"undefined"==typeof f.lang&&(f.lang=a("html").attr("lang").replace("-","_")),f.cacheKey="core_str/"+f.key+"/"+f.component+"/"+f.lang,"undefined"==typeof M.str[f.component]||"undefined"==typeof M.str[f.component][f.key]){var k=c.get(f.cacheKey);k?("undefined"==typeof M.str[f.component]&&(M.str[f.component]=[]),M.str[f.component][f.key]=k):j=!0}if(j){var l=[],m=[],n=function(a){this.resolve(a)};for(i=0;i<e.length;i++)if(f=e[i],"undefined"!=typeof d[f.cacheKey])m.push(d[f.cacheKey]);else{var o=a.Deferred();l.push({methodname:"core_get_string",args:{stringid:f.key,component:f.component,lang:f.lang,stringparams:[]},done:n.bind(o)}),d[f.cacheKey]=o.promise(),m.push(d[f.cacheKey])}l.length>0&&b.call(l,!0,!1),a.when.apply(null,m).done(function(){var a=0;for(a=0;a<arguments.length;a++)f=e[a],"undefined"==typeof M.str[f.component]&&(M.str[f.component]=[]),M.str[f.component][f.key]=arguments[a],c.set("core_str/"+f.key+"/"+f.component+"/"+f.lang,arguments[a]),h[a]=M.util.get_string(f.key,f.component,f.param).trim();g.resolve(h)}).fail(function(a){g.reject(a)})}else{for(i=0;i<e.length;i++)f=e[i],h[i]=M.util.get_string(f.key,f.component,f.param);g.resolve(h)}return g.promise()}}});
\ No newline at end of file
diff --git a/lib/amd/src/str.js b/lib/amd/src/str.js
index ad14a0af26e..6e9407b277c 100644
--- a/lib/amd/src/str.js
+++ b/lib/amd/src/str.js
@@ -28,6 +28,7 @@
 /* eslint-disable no-restricted-properties */
 define(['jquery', 'core/ajax', 'core/localstorage'], function($, ajax, storage) {
 
+    var promiseCache = [];
 
     return /** @alias module:core/str */ {
         // Public variables and functions.
@@ -71,6 +72,7 @@ define(['jquery', 'core/ajax', 'core/localstorage'], function($, ajax, storage)
             var i = 0;
             var missing = false;
             var request;
+
             // Try from local storage. If it's there - put it in M.str and resolve it.
 
             for (i = 0; i < requests.length; i++) {
@@ -78,10 +80,11 @@ define(['jquery', 'core/ajax', 'core/localstorage'], function($, ajax, storage)
                 if (typeof request.lang === "undefined") {
                     request.lang = $('html').attr('lang').replace('-', '_');
                 }
+                request.cacheKey = 'core_str/' + request.key + '/' + request.component + '/' + request.lang;
                 if (typeof M.str[request.component] === "undefined" ||
                         typeof M.str[request.component][request.key] === "undefined") {
                     // Try and revive it from local storage.
-                    var cached = storage.get('core_str/' + request.key + '/' + request.component + '/' + request.lang);
+                    var cached = storage.get(request.cacheKey);
                     if (cached) {
                         if (typeof M.str[request.component] === "undefined") {
                             M.str[request.component] = [];
@@ -103,25 +106,47 @@ define(['jquery', 'core/ajax', 'core/localstorage'], function($, ajax, storage)
                 }
                 deferred.resolve(results);
             } else {
-                // Something is missing, we might as well load them all.
                 var ajaxrequests = [];
+                var fetchpromises = [];
+
+                // Done handler for ajax call. Must be bound to the current fetchpromise. We do this
+                // to avoid creating a function in a loop.
+                var doneFunc = function(str) {
+                    this.resolve(str);
+                };
 
                 for (i = 0; i < requests.length; i++) {
                     request = requests[i];
 
-                    ajaxrequests.push({
-                        methodname: 'core_get_string',
-                        args: {
-                            stringid: request.key,
-                            component: request.component,
-                            lang: request.lang,
-                            stringparams: []
-                        }
-                    });
+                    // If we ever fetched this string with a promise, reuse it.
+                    if (typeof promiseCache[request.cacheKey] !== 'undefined') {
+                        fetchpromises.push(promiseCache[request.cacheKey]);
+                    } else {
+                        // Add this to the list we need to really fetch.
+                        var fetchpromise = $.Deferred();
+
+                        ajaxrequests.push({
+                            methodname: 'core_get_string',
+                            args: {
+                                stringid: request.key,
+                                component: request.component,
+                                lang: request.lang,
+                                stringparams: []
+                            },
+                            done: doneFunc.bind(fetchpromise)
+                        });
+
+                        promiseCache[request.cacheKey] = fetchpromise.promise();
+                        fetchpromises.push(promiseCache[request.cacheKey]);
+                    }
                 }
 
-                var deferreds = ajax.call(ajaxrequests, true, false);
-                $.when.apply(null, deferreds).done(
+                // Everything might already be queued so we need to check if we have real ajax requests to run.
+                if (ajaxrequests.length > 0) {
+                    ajax.call(ajaxrequests, true, false);
+                }
+
+                $.when.apply(null, fetchpromises).done(
                     function() {
                         // Turn the list of arguments (unknown length) into a real array.
                         var i = 0;