diff --git a/fluent-react/examples/fallback-async/package.json b/fluent-react/examples/fallback-async/package.json
new file mode 100644
index 000000000..357a34ba7
--- /dev/null
+++ b/fluent-react/examples/fallback-async/package.json
@@ -0,0 +1,20 @@
+{
+  "name": "fluent-react-example-fallback-async",
+  "version": "0.1.0",
+  "private": true,
+  "devDependencies": {
+    "react-scripts": "1.1.0"
+  },
+  "dependencies": {
+    "babel-polyfill": "^6.26.0",
+    "fluent": "file:../../../fluent",
+    "fluent-intl-polyfill": "file:../../../fluent-intl-polyfill",
+    "fluent-react": "file:../../../fluent-react",
+    "react": "^16.2.0",
+    "react-dom": "^16.2.0"
+  },
+  "scripts": {
+    "start": "react-scripts start",
+    "build": "react-scripts build"
+  }
+}
diff --git a/fluent-react/examples/fallback-async/public/index.html b/fluent-react/examples/fallback-async/public/index.html
new file mode 100644
index 000000000..0e506c233
--- /dev/null
+++ b/fluent-react/examples/fallback-async/public/index.html
@@ -0,0 +1,11 @@
+
+
+  
+    
+    
+    Fallback Async - a fluent-react example
+  
+  
+    
+  
+
diff --git a/fluent-react/examples/fallback-async/src/App.js b/fluent-react/examples/fallback-async/src/App.js
new file mode 100644
index 000000000..16fcec3ea
--- /dev/null
+++ b/fluent-react/examples/fallback-async/src/App.js
@@ -0,0 +1,28 @@
+import React from 'react';
+import { Localized } from 'fluent-react/compat';
+
+export default function App() {
+  return (
+    
+      
+        This example is hardcoded to use ['pl', 'en-US'].
+      
+
+      
+        Foo
+      
+
+      
+        Bar
+      
+
+      
+        Baz is missing from all locales
+      
+
+      
+        {'Qux is like { $baz }: missing from all locales.'}
+      
+    
 
+  );
+}
diff --git a/fluent-react/examples/fallback-async/src/index.js b/fluent-react/examples/fallback-async/src/index.js
new file mode 100644
index 000000000..63c002097
--- /dev/null
+++ b/fluent-react/examples/fallback-async/src/index.js
@@ -0,0 +1,14 @@
+import 'babel-polyfill';
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { LocalizationProvider } from 'fluent-react/compat';
+
+import { generateMessages } from './l10n';
+import App from './App';
+
+ReactDOM.render(
+  
+    
+  ,
+  document.getElementById('root')
+);
diff --git a/fluent-react/examples/fallback-async/src/l10n.js b/fluent-react/examples/fallback-async/src/l10n.js
new file mode 100644
index 000000000..769cb7071
--- /dev/null
+++ b/fluent-react/examples/fallback-async/src/l10n.js
@@ -0,0 +1,45 @@
+import 'fluent-intl-polyfill/compat';
+import { MessageContext } from 'fluent/compat';
+
+const MESSAGES_ALL = {
+  'pl': `
+foo = Foo po polsku
+  `,
+  'en-US': `
+foo = Foo in English
+bar = Bar in English
+  `,
+};
+
+
+// create-react-app is rather outdated at this point when it comes to modern JS
+// support. It's not configured to understand async generators. The function
+// below is equivalent to the following generator function:
+//
+//     export async function* generateMessages() {
+//       for (const locale of ['pl', 'en-US']) {
+//         const cx = new MessageContext(locale);
+//         cx.addMessages(MESSAGES_ALL[locale]);
+//         yield cx;
+//       }
+//    }
+
+export function generateMessages() {
+  const locales = ["pl", "en-US"];
+  return {
+    [Symbol.asyncIterator]() {
+      return this;
+    },
+    async next() {
+      const locale = locales.shift();
+
+      if (locale === undefined) {
+        return {value: undefined, done: true};
+      }
+
+      const cx = new MessageContext(locale);
+      cx.addMessages(MESSAGES_ALL[locale]);
+      return {value: cx, done: false};
+    }
+  };
+}
diff --git a/fluent-react/makefile b/fluent-react/makefile
index c0f6470a3..78f085298 100644
--- a/fluent-react/makefile
+++ b/fluent-react/makefile
@@ -1,6 +1,6 @@
 PACKAGE := fluent-react
 GLOBAL  := FluentReact
-DEPS    := fluent:Fluent,react:React,prop-types:PropTypes
+DEPS    := fluent:Fluent,cached-iterable:CachedIterable,react:React,prop-types:PropTypes
 
 include ../common.mk
 
diff --git a/fluent-react/src/cached_async_iterable.js b/fluent-react/src/cached_async_iterable.js
new file mode 100644
index 000000000..381515eb7
--- /dev/null
+++ b/fluent-react/src/cached_async_iterable.js
@@ -0,0 +1,102 @@
+/*
+ * CachedAsyncIterable caches the elements yielded by an async iterable.
+ *
+ * It can be used to iterate over an iterable many times without depleting the
+ * iterable.
+ */
+export default class CachedAsyncIterable {
+    /**
+     * Create an `CachedAsyncIterable` instance.
+     *
+     * @param {Iterable} iterable
+     * @returns {CachedAsyncIterable}
+     */
+    constructor(iterable) {
+        if (Symbol.asyncIterator in Object(iterable)) {
+            this.iterator = iterable[Symbol.asyncIterator]();
+        } else if (Symbol.iterator in Object(iterable)) {
+            this.iterator = iterable[Symbol.iterator]();
+        } else {
+            throw new TypeError("Argument must implement the iteration protocol.");
+        }
+
+        this.seen = [];
+    }
+
+    /**
+     * Create an `CachedAsyncIterable` instance from an iterable or, if
+     * another instance of `CachedAsyncIterable` is passed, return it
+     * without any modifications.
+     *
+     * @param {Iterable} iterable
+     * @returns {CachedAsyncIterable}
+     */
+    static from(iterable) {
+        if (iterable instanceof CachedAsyncIterable) {
+            return iterable;
+        }
+
+        return new CachedAsyncIterable(iterable);
+    }
+
+    /**
+     * Synchronous iterator over the cached elements.
+     *
+     * Return a generator object implementing the iterator protocol over the
+     * cached elements of the original (async or sync) iterable.
+     */
+    [Symbol.iterator]() {
+        const {seen} = this;
+        let cur = 0;
+
+        return {
+            next() {
+                if (seen.length === cur) {
+                    return {value: undefined, done: true};
+                }
+                return seen[cur++];
+            }
+        };
+    }
+
+    /**
+     * Asynchronous iterator caching the yielded elements.
+     *
+     * Elements yielded by the original iterable will be cached and available
+     * synchronously. Returns an async generator object implementing the
+     * iterator protocol over the elements of the original (async or sync)
+     * iterable.
+     */
+    [Symbol.asyncIterator]() {
+        const { seen, iterator } = this;
+        let cur = 0;
+
+        return {
+            async next() {
+                if (seen.length <= cur) {
+                    seen.push(await iterator.next());
+                }
+                return seen[cur++];
+            }
+        };
+    }
+
+    /**
+     * This method allows user to consume the next element from the iterator
+     * into the cache.
+     *
+     * @param {number} count - number of elements to consume
+     */
+    async touchNext(count = 1) {
+        const { seen, iterator } = this;
+        let idx = 0;
+        while (idx++ < count) {
+            if (seen.length === 0 || seen[seen.length - 1].done === false) {
+                seen.push(await iterator.next());
+            }
+        }
+        // Return the last cached {value, done} object to allow the calling
+        // code to decide if it needs to call touchNext again.
+        return seen[seen.length - 1];
+    }
+}
diff --git a/fluent-react/src/localization.js b/fluent-react/src/localization.js
index 70089486d..f99e608ee 100644
--- a/fluent-react/src/localization.js
+++ b/fluent-react/src/localization.js
@@ -1,4 +1,5 @@
-import { CachedIterable, mapContextSync } from "fluent";
+import {mapContextSync} from "fluent";
+import CachedAsyncIterable from "./cached_async_iterable";
 
 /*
  * `ReactLocalization` handles translation formatting and fallback.
@@ -17,8 +18,10 @@ import { CachedIterable, mapContextSync } from "fluent";
  */
 export default class ReactLocalization {
   constructor(messages) {
-    this.contexts = new CachedIterable(messages);
+    this.contexts = CachedAsyncIterable.from(messages);
     this.subs = new Set();
+
+    this.isFetching = false;
   }
 
   /*
@@ -39,14 +42,39 @@ export default class ReactLocalization {
    * Set a new `messages` iterable and trigger the retranslation.
    */
   setMessages(messages) {
-    this.contexts = new CachedIterable(messages);
-
-    // Update all subscribed Localized components.
-    this.subs.forEach(comp => comp.relocalize());
+    this.contexts = CachedAsyncIterable.from(messages);
+    if (this.contexts.length === 0) {
+      // If the iterable has an empty cache, request the first context but do
+      // not await it.
+      this.touchNext(1);
+    } else {
+      // Otherwise, let's try to use the new cached contexts. Update all
+      // subscribed Localized components.
+      this.subs.forEach(comp => comp.relocalize());
+    }
   }
 
   getMessageContext(id) {
-    return mapContextSync(this.contexts, id);
+    const mcx = mapContextSync(this.contexts, id);
+    if (mcx === null && !this.isFetching) {
+      // Request the next context but do not await it.
+      this.touchNext(1);
+    }
+    return mcx;
+  }
+
+  async touchNext(count = 1) {
+    // Fetch the first `count` contexts.
+    this.isFetching = true;
+    const last = await this.contexts.touchNext(count);
+    this.isFetching = false;
+
+    if (!last.done) {
+      // If the iterable of contexts has not been exhausted yet, the newly
+      // fetched context might offer fallback translations. Update all
+      // subscribed Localized components.
+      this.subs.forEach(comp => comp.relocalize());
+    }
   }
 
   formatCompound(mcx, msg, args) {
diff --git a/fluent-react/src/provider.js b/fluent-react/src/provider.js
index 53cfd8b84..cfeafffb3 100644
--- a/fluent-react/src/provider.js
+++ b/fluent-react/src/provider.js
@@ -30,7 +30,7 @@ export default class LocalizationProvider extends Component {
       throw new Error("LocalizationProvider must receive the messages prop.");
     }
 
-    if (!messages[Symbol.iterator]) {
+    if (!messages[Symbol.iterator] && !messages[Symbol.asyncIterator]) {
       throw new Error("The messages prop must be an iterable.");
     }
 
@@ -72,6 +72,10 @@ function isIterable(props, propName, componentName) {
     return null;
   }
 
+  if (Symbol.asyncIterator in Object(prop)) {
+    return null;
+  }
+
   return new Error(
     `The ${propName} prop supplied to ${componentName} must be an iterable.`
   );
diff --git a/fluent-react/test/localized_change_test.js b/fluent-react/test/localized_change_test.js
index 9d997728b..9b8134ff2 100644
--- a/fluent-react/test/localized_change_test.js
+++ b/fluent-react/test/localized_change_test.js
@@ -3,12 +3,15 @@ import assert from 'assert';
 import { shallow } from 'enzyme';
 import { MessageContext } from '../../fluent/src';
 import ReactLocalization from '../src/localization';
+import CachedAsyncIterable from '../src/cached_async_iterable';
 import { Localized } from '../src/index';
 
-suite('Localized - change messages', function() {
-  test('relocalizing', function() {
+suite.only('Localized - change messages', function() {
+  test('relocalizing', async function() {
     const mcx1 = new MessageContext();
-    const l10n = new ReactLocalization([mcx1]);
+    const contexts1 = new CachedAsyncIterable([mcx1]);
+    await contexts1.touchNext(1);
+    const l10n = new ReactLocalization(contexts1);
 
     mcx1.addMessages(`
 foo = FOO
@@ -30,7 +33,9 @@ foo = FOO
 foo = BAR
 `);
 
-    l10n.setMessages([mcx2]);
+    const contexts2 = new CachedAsyncIterable([mcx2]);
+    await contexts2.touchNext(1);
+    l10n.setMessages(contexts2);
 
     wrapper.update();
     assert.ok(wrapper.contains(
diff --git a/fluent-react/test/localized_render_test.js b/fluent-react/test/localized_render_test.js
index 7ccb25e9f..a968dfb03 100644
--- a/fluent-react/test/localized_render_test.js
+++ b/fluent-react/test/localized_render_test.js
@@ -4,13 +4,20 @@ import sinon from 'sinon';
 import { shallow } from 'enzyme';
 import { MessageContext } from '../../fluent/src';
 import ReactLocalization from '../src/localization';
+import CachedAsyncIterable from '../src/cached_async_iterable';
 import { Localized } from '../src/index';
 
 suite('Localized - rendering', function() {
-  test('render the value', function() {
-    const mcx = new MessageContext();
-    const l10n = new ReactLocalization([mcx]);
+  let mcx, l10n;
 
+  setup(async function() {
+    mcx = new MessageContext();
+    const contexts = new CachedAsyncIterable([mcx]);
+    await contexts.touchNext(1);
+    l10n = new ReactLocalization(contexts);
+  });
+
+  test('render the value', async function() {
     mcx.addMessages(`
 foo = FOO
 `)
@@ -28,9 +35,6 @@ foo = FOO
   });
 
   test('render an allowed attribute', function() {
-    const mcx = new MessageContext();
-    const l10n = new ReactLocalization([mcx]);
-
     mcx.addMessages(`
 foo =
     .attr = ATTR
@@ -49,9 +53,6 @@ foo =
   });
 
   test('only render allowed attributes', function() {
-    const mcx = new MessageContext();
-    const l10n = new ReactLocalization([mcx]);
-
     mcx.addMessages(`
 foo =
     .attr1 = ATTR 1
@@ -71,9 +72,6 @@ foo =
   });
 
   test('filter out forbidden attributes', function() {
-    const mcx = new MessageContext();
-    const l10n = new ReactLocalization([mcx]);
-
     mcx.addMessages(`
 foo =
     .attr = ATTR
@@ -92,9 +90,6 @@ foo =
   });
 
   test('filter all attributes if attrs not given', function() {
-    const mcx = new MessageContext();
-    const l10n = new ReactLocalization([mcx]);
-
     mcx.addMessages(`
 foo =
     .attr = ATTR
@@ -113,9 +108,6 @@ foo =
   });
 
   test('preserve existing attributes when setting new ones', function() {
-    const mcx = new MessageContext();
-    const l10n = new ReactLocalization([mcx]);
-
     mcx.addMessages(`
 foo =
     .attr = ATTR
@@ -134,9 +126,6 @@ foo =
   });
 
   test('overwrite existing attributes if allowed', function() {
-    const mcx = new MessageContext();
-    const l10n = new ReactLocalization([mcx]);
-
     mcx.addMessages(`
 foo =
     .existing = ATTR
@@ -155,9 +144,6 @@ foo =
   });
 
   test('protect existing attributes if setting is forbidden', function() {
-    const mcx = new MessageContext();
-    const l10n = new ReactLocalization([mcx]);
-
     mcx.addMessages(`
 foo =
     .existing = ATTR
@@ -176,9 +162,6 @@ foo =
   });
 
   test('protect existing attributes by default', function() {
-    const mcx = new MessageContext();
-    const l10n = new ReactLocalization([mcx]);
-
     mcx.addMessages(`
 foo =
     .existing = ATTR
@@ -197,9 +180,6 @@ foo =
   });
 
   test('preserve children when translation value is null', function() {
-    const mcx = new MessageContext();
-    const l10n = new ReactLocalization([mcx]);
-
     mcx.addMessages(`
 foo =
     .title = TITLE
@@ -223,9 +203,7 @@ foo =
 
 
   test('$arg is passed to format the value', function() {
-    const mcx = new MessageContext();
     const format = sinon.spy(mcx, 'format');
-    const l10n = new ReactLocalization([mcx]);
 
     mcx.addMessages(`
 foo = { $arg }
@@ -243,9 +221,7 @@ foo = { $arg }
   });
 
   test('$arg is passed to format the attributes', function() {
-    const mcx = new MessageContext();
     const format = sinon.spy(mcx, 'format');
-    const l10n = new ReactLocalization([mcx]);
 
     mcx.addMessages(`
 foo = { $arg }