For now, only one `pyodide` can be loaded in a given global scope because we need to use a global variable for package loading. This changes `loadPyodide` so that it no longer defines `globalThis.pyodide` in favor of just returning `pyodide` from `loadPyodide`. `Module` is placed on the global object as `globalThis.__pyodide_module` for the purposes of package loading. I added documentation to explicitly state that `loadPyodide` can only be run once in each global Javascript context, an error will be thrown otherwise.

This has two advantages: 
1. The returned `pyodide` module is understandable to Typescript and other code analysis, whereas the global variable is not understandable.
2. If in the future we are able to remove the global variable, no further API change will be needed (other than relaxing the documented "pyodide must be globally-unique" constraint). I figure it's better to make this API change earlier rather than later.

It will be possible to completely remove the global variable dependence when Firefox implements support for using ES6 modules in webworkers (see https://bugzilla.mozilla.org/show_bug.cgi?id=1247687).
API Don't expose pyodide as a global variable in loadPyodide. For now, only one `pyodide` can be loaded in a given global scope because we need to use a global variable for package loading. This changes `loadPyodide` so that it no longer defines `globalThis.pyodide` in favor of just returning `pyodide` from `loadPyodide`. `Module` is placed on the global object as `globalThis.__pyodide_module` for the purposes of package loading. I added documentation to explicitly state that `loadPyodide` can only be run once in each global Javascript context, an error will be thrown otherwise.

This has two advantages: 
1. The returned `pyodide` module is understandable to Typescript and other code analysis, whereas the global variable is not understandable.
2. If in the future we are able to remove the global variable, no further API change will be needed (other than relaxing the documented "pyodide must be globally-unique" constraint). I figure it's better to make this API change earlier rather than later.

It will be possible to completely remove the global variable dependence when Firefox implements support for using ES6 modules in webworkers (see https://bugzilla.mozilla.org/show_bug.cgi?id=1247687).
import sys
from pathlib import Path

sys.path.append(str(Path(__file__).resolve().parents[2] / "src" / "py"))

import pytest  # type: ignore
import time
from pyodide._base import eval_code_async
import asyncio


def test_await_jsproxy(selenium):
    selenium.run(
        """
        def prom(res,rej):
            global resolve
            resolve = res
        from js import Promise
        p = Promise.new(prom)
        async def temp():
            x = await p
            return x + 7
        resolve(10)
        c = temp()
        r = c.send(None)
        """
    )
    time.sleep(0.01)
    msg = "StopIteration: 17"
    with pytest.raises(selenium.JavascriptException, match=msg):
        selenium.run(
            """
            c.send(r.result())
            """
        )


def test_then_jsproxy(selenium):
    selenium.run(
        """
        def prom(res, rej):
            global resolve
            global reject
            resolve = res
            reject = rej

        from js import Promise
        result = None
        err = None
        finally_occurred = False

        def onfulfilled(value):
            global result
            result = value

        def onrejected(value):
            global err
            err = value

        def onfinally():
            global finally_occurred
            finally_occurred = True
        """
    )

    selenium.run(
        """
        p = Promise.new(prom)
        p.then(onfulfilled, onrejected)
        resolve(10)
        """
    )
    time.sleep(0.01)
    selenium.run(
        """
        assert result == 10
        assert err is None
        result = None
        """
    )

    selenium.run(
        """
        p = Promise.new(prom)
        p.then(onfulfilled, onrejected)
        reject(10)
        """
    )
    time.sleep(0.01)
    selenium.run(
        """
        assert result is None
        assert err == 10
        err = None
        """
    )

    selenium.run(
        """
        p = Promise.new(prom)
        p.catch(onrejected)
        resolve(10)
        """
    )
    time.sleep(0.01)
    selenium.run("assert err is None")

    selenium.run(
        """
        p = Promise.new(prom)
        p.catch(onrejected)
        reject(10)
        """
    )
    time.sleep(0.01)
    selenium.run(
        """
        assert err == 10
        err = None
        """
    )

    selenium.run(
        """
        p = Promise.new(prom)
        p.finally_(onfinally)
        resolve(10)
        """
    )
    time.sleep(0.01)
    selenium.run(
        """
        assert finally_occurred
        finally_occurred = False
        """
    )

    selenium.run(
        """
        p = Promise.new(prom)
        p.finally_(onfinally)
        reject(10)
        """
    )
    time.sleep(0.01)
    selenium.run(
        """
        assert finally_occurred
        finally_occurred = False
        """
    )


def test_await_fetch(selenium):
    selenium.run(
        """
        from js import fetch
        async def test():
            response = await fetch("console.html")
            result = await response.text()
            print(result)
            return result

        c = test()
        r1 = c.send(None)
        """
    )
    time.sleep(0.1)
    selenium.run(
        """
        r2 = c.send(r1.result())
        """
    )
    time.sleep(0.1)
    msg = "StopIteration: <!doctype html>"
    with pytest.raises(selenium.JavascriptException, match=msg):
        selenium.run(
            """
            c.send(r2.result())
            """
        )


def test_await_error(selenium):
    selenium.run_js(
        """
        async function async_js_raises(){
            throw new Error("This is an error message!");
        }
        window.async_js_raises = async_js_raises;
        function js_raises(){
            throw new Error("This is an error message!");
        }
        window.js_raises = js_raises;
        pyodide.runPython(`
            from js import async_js_raises, js_raises
            async def test():
                c = await async_js_raises()
                return c
            c = test()
            r1 = c.send(None)
        `);
        """
    )
    msg = "This is an error message!"
    with pytest.raises(selenium.JavascriptException, match=msg):
        # Wait for event loop to go around for chome
        selenium.run(
            """
            r2 = c.send(r1.result())
            """
        )


def test_eval_code_async_simple():
    c = eval_code_async("1+92")
    with pytest.raises(StopIteration, match="93"):
        c.send(None)


def test_eval_code_async_loop():
    async def slow_identity(i):
        await asyncio.sleep(0.1)
        return i

    c = eval_code_async(
        """
        tot = 0
        for i in range(10):
            tot += await slow_identity(i)
        tot
        """,
        globals=globals(),
        locals=locals(),
    )
    fut = asyncio.ensure_future(c)
    asyncio.get_event_loop().run_until_complete(fut)
    assert fut.result() == 45


def test_eval_code_await_jsproxy(selenium):
    selenium.run(
        """
        def prom(res,rej):
            global resolve
            resolve = res
        from js import Promise
        p = Promise.new(prom)
        from pyodide._base import eval_code_async
        c = eval_code_async(
            '''
            x = await p
            x + 7
            ''',
            globals=globals()
        )
        resolve(10)
        r = c.send(None)
        """
    )
    time.sleep(0.01)
    msg = "StopIteration: 17"
    with pytest.raises(selenium.JavascriptException, match=msg):
        selenium.run(
            """
            c.send(r.result())
            """
        )


def test_eval_code_await_fetch(selenium):
    selenium.run(
        """
        from js import fetch
        from pyodide._base import eval_code_async
        c = eval_code_async(
            '''
            response = await fetch("console.html")
            await response.text()
            ''',
            globals=globals()
        )
        r1 = c.send(None)
        """
    )
    time.sleep(0.1)
    selenium.run(
        """
        r2 = c.send(r1.result())
        """
    )
    time.sleep(0.1)
    msg = "StopIteration: <!doctype html>"
    with pytest.raises(selenium.JavascriptException, match=msg):
        selenium.run(
            """
            c.send(r2.result())
            """
        )


def test_eval_code_await_error(selenium):
    selenium.run_js(
        """
        async function async_js_raises(){
            console.log("Hello there???");
            throw new Error("This is an error message!");
        }
        window.async_js_raises = async_js_raises;
        pyodide.runPython(`
            from js import async_js_raises
            from pyodide._base import eval_code_async
            c = eval_code_async(
                '''
                await async_js_raises()
                ''',
                globals=globals()
            )
            r1 = c.send(None)
        `)
        """
    )
    msg = "This is an error message!"
    with pytest.raises(selenium.JavascriptException, match=msg):
        # Wait for event loop to go around for chome
        selenium.run(
            """
            r2 = c.send(r1.result())
            """
        )


def test_ensure_future_memleak(selenium):
    selenium.run_js(
        """
        window.o = { "xxx" : 777 };
        pyodide.runPython(`
            import asyncio
            from js import o
            async def test():
                return o
            asyncio.ensure_future(test())
            None
        `);
        """
    )


def test_await_pyproxy_eval_async(selenium):
    assert (
        selenium.run_js(
            """
            let c = pyodide._module.pyodide_py._base.eval_code_async("1+1");
            let result = await c;
            c.destroy();
            return result;
            """
        )
        == 2
    )

    assert (
        selenium.run_js(
            """
            let finally_occurred = false;
            let c = pyodide._module.pyodide_py._base.eval_code_async("1+1");
            let result = await c.finally(() => { finally_occurred = true; });
            return [result, finally_occurred];
            """
        )
        == [2, True]
    )

    assert (
        selenium.run_js(
            """
            let finally_occurred = false;
            let err_occurred = false;
            let c = pyodide._module.pyodide_py._base.eval_code_async("raise ValueError('hi')");
            try {
                let result = await c.finally(() => { finally_occurred = true; });
            } catch(e){
                err_occurred = e.constructor.name === "PythonError";
            }
            return [finally_occurred, err_occurred];
            """
        )
        == [True, True]
    )

    assert selenium.run_js(
        """
        let c = pyodide._module.pyodide_py._base.eval_code_async("raise ValueError('hi')");
        return await c.catch(e => e.constructor.name === "PythonError");
        """
    )

    assert selenium.run_js(
        """
        let c = pyodide._module.pyodide_py._base.eval_code_async(`
            from js import fetch
            await (await fetch('packages.json')).json()
        `);
        let packages = await c;
        c.destroy();
        return (!!packages.dependencies) && (!!packages.import_name_to_package_name);
        """
    )

    assert selenium.run_js(
        """
        let c = pyodide._module.pyodide_py._base.eval_code_async("1+1");
        await c;
        let err_occurred = false;
        try {
            // Triggers: cannot await already awaited coroutine
            await c;
        } catch(e){
            err_occurred = true;
        }
        return err_occurred;
        """
    )


def test_await_pyproxy_async_def(selenium):
    assert selenium.run_js(
        """
        let packages = await pyodide.runPython(`
            from js import fetch
            async def temp():
                return await (await fetch('packages.json')).json()
            temp()
        `);
        return (!!packages.dependencies) && (!!packages.import_name_to_package_name);
        """
    )

import sys
from pathlib import Path

sys.path.append(str(Path(__file__).resolve().parents[2] / "src" / "py"))

import pytest  # type: ignore
import time
from pyodide._base import eval_code_async
import asyncio


def test_await_jsproxy(selenium):
    selenium.run(
        """
        def prom(res,rej):
            global resolve
            resolve = res
        from js import Promise
        p = Promise.new(prom)
        async def temp():
            x = await p
            return x + 7
        resolve(10)
        c = temp()
        r = c.send(None)
        """
    )
    time.sleep(0.01)
    msg = "StopIteration: 17"
    with pytest.raises(selenium.JavascriptException, match=msg):
        selenium.run(
            """
            c.send(r.result())
            """
        )


def test_then_jsproxy(selenium):
    selenium.run(
        """
        def prom(res, rej):
            global resolve
            global reject
            resolve = res
            reject = rej

        from js import Promise
        result = None
        err = None
        finally_occurred = False

        def onfulfilled(value):
            global result
            result = value

        def onrejected(value):
            global err
            err = value

        def onfinally():
            global finally_occurred
            finally_occurred = True
        """
    )

    selenium.run(
        """
        p = Promise.new(prom)
        p.then(onfulfilled, onrejected)
        resolve(10)
        """
    )
    time.sleep(0.01)
    selenium.run(
        """
        assert result == 10
        assert err is None
        result = None
        """
    )

    selenium.run(
        """
        p = Promise.new(prom)
        p.then(onfulfilled, onrejected)
        reject(10)
        """
    )
    time.sleep(0.01)
    selenium.run(
        """
        assert result is None
        assert err == 10
        err = None
        """
    )

    selenium.run(
        """
        p = Promise.new(prom)
        p.catch(onrejected)
        resolve(10)
        """
    )
    time.sleep(0.01)
    selenium.run("assert err is None")

    selenium.run(
        """
        p = Promise.new(prom)
        p.catch(onrejected)
        reject(10)
        """
    )
    time.sleep(0.01)
    selenium.run(
        """
        assert err == 10
        err = None
        """
    )

    selenium.run(
        """
        p = Promise.new(prom)
        p.finally_(onfinally)
        resolve(10)
        """
    )
    time.sleep(0.01)
    selenium.run(
        """
        assert finally_occurred
        finally_occurred = False
        """
    )

    selenium.run(
        """
        p = Promise.new(prom)
        p.finally_(onfinally)
        reject(10)
        """
    )
    time.sleep(0.01)
    selenium.run(
        """
        assert finally_occurred
        finally_occurred = False
        """
    )


def test_await_fetch(selenium):
    selenium.run(
        """
        from js import fetch
        async def test():
            response = await fetch("console.html")
            result = await response.text()
            print(result)
            return result

        c = test()
        r1 = c.send(None)
        """
    )
    time.sleep(0.1)
    selenium.run(
        """
        r2 = c.send(r1.result())
        """
    )
    time.sleep(0.1)
    msg = "StopIteration: <!doctype html>"
    with pytest.raises(selenium.JavascriptException, match=msg):
        selenium.run(
            """
            c.send(r2.result())
            """
        )


def test_await_error(selenium):
    selenium.run_js(
        """
        async function async_js_raises(){
            throw new Error("This is an error message!");
        }
        window.async_js_raises = async_js_raises;
        function js_raises(){
            throw new Error("This is an error message!");
        }
        window.js_raises = js_raises;
        pyodide.runPython(`
            from js import async_js_raises, js_raises
            async def test():
                c = await async_js_raises()
                return c
            c = test()
            r1 = c.send(None)
        `);
        """
    )
    msg = "This is an error message!"
    with pytest.raises(selenium.JavascriptException, match=msg):
        # Wait for event loop to go around for chome
        selenium.run(
            """
            r2 = c.send(r1.result())
            """
        )


def test_eval_code_async_simple():
    c = eval_code_async("1+92")
    with pytest.raises(StopIteration, match="93"):
        c.send(None)


def test_eval_code_async_loop():
    async def slow_identity(i):
        await asyncio.sleep(0.1)
        return i

    c = eval_code_async(
        """
        tot = 0
        for i in range(10):
            tot += await slow_identity(i)
        tot
        """,
        globals=globals(),
        locals=locals(),
    )
    fut = asyncio.ensure_future(c)
    asyncio.get_event_loop().run_until_complete(fut)
    assert fut.result() == 45


def test_eval_code_await_jsproxy(selenium):
    selenium.run(
        """
        def prom(res,rej):
            global resolve
            resolve = res
        from js import Promise
        p = Promise.new(prom)
        from pyodide._base import eval_code_async
        c = eval_code_async(
            '''
            x = await p
            x + 7
            ''',
            globals=globals()
        )
        resolve(10)
        r = c.send(None)
        """
    )
    time.sleep(0.01)
    msg = "StopIteration: 17"
    with pytest.raises(selenium.JavascriptException, match=msg):
        selenium.run(
            """
            c.send(r.result())
            """
        )


def test_eval_code_await_fetch(selenium):
    selenium.run(
        """
        from js import fetch
        from pyodide._base import eval_code_async
        c = eval_code_async(
            '''
            response = await fetch("console.html")
            await response.text()
            ''',
            globals=globals()
        )
        r1 = c.send(None)
        """
    )
    time.sleep(0.1)
    selenium.run(
        """
        r2 = c.send(r1.result())
        """
    )
    time.sleep(0.1)
    msg = "StopIteration: <!doctype html>"
    with pytest.raises(selenium.JavascriptException, match=msg):
        selenium.run(
            """
            c.send(r2.result())
            """
        )


def test_eval_code_await_error(selenium):
    selenium.run_js(
        """
        async function async_js_raises(){
            console.log("Hello there???");
            throw new Error("This is an error message!");
        }
        window.async_js_raises = async_js_raises;
        pyodide.runPython(`
            from js import async_js_raises
            from pyodide._base import eval_code_async
            c = eval_code_async(
                '''
                await async_js_raises()
                ''',
                globals=globals()
            )
            r1 = c.send(None)
        `)
        """
    )
    msg = "This is an error message!"
    with pytest.raises(selenium.JavascriptException, match=msg):
        # Wait for event loop to go around for chome
        selenium.run(
            """
            r2 = c.send(r1.result())
            """
        )


def test_ensure_future_memleak(selenium):
    selenium.run_js(
        """
        window.o = { "xxx" : 777 };
        pyodide.runPython(`
            import asyncio
            from js import o
            async def test():
                return o
            asyncio.ensure_future(test())
            None
        `);
        """
    )


def test_await_pyproxy_eval_async(selenium):
    assert (
        selenium.run_js(
            """
            let c = pyodide._module.pyodide_py._base.eval_code_async("1+1");
            let result = await c;
            c.destroy();
            return result;
            """
        )
        == 2
    )

    assert (
        selenium.run_js(
            """
            let finally_occurred = false;
            let c = pyodide._module.pyodide_py._base.eval_code_async("1+1");
            let result = await c.finally(() => { finally_occurred = true; });
            return [result, finally_occurred];
            """
        )
        == [2, True]
    )

    assert (
        selenium.run_js(
            """
            let finally_occurred = false;
            let err_occurred = false;
            let c = pyodide._module.pyodide_py._base.eval_code_async("raise ValueError('hi')");
            try {
                let result = await c.finally(() => { finally_occurred = true; });
            } catch(e){
                err_occurred = e.constructor.name === "PythonError";
            }
            return [finally_occurred, err_occurred];
            """
        )
        == [True, True]
    )

    assert selenium.run_js(
        """
        let c = pyodide._module.pyodide_py._base.eval_code_async("raise ValueError('hi')");
        return await c.catch(e => e.constructor.name === "PythonError");
        """
    )

    assert selenium.run_js(
        """
        let c = pyodide._module.pyodide_py._base.eval_code_async(`
            from js import fetch
            await (await fetch('packages.json')).json()
        `);
        let packages = await c;
        c.destroy();
        return (!!packages.dependencies) && (!!packages.import_name_to_package_name);
        """
    )

    assert selenium.run_js(
        """
        let c = pyodide._module.pyodide_py._base.eval_code_async("1+1");
        await c;
        let err_occurred = false;
        try {
            // Triggers: cannot await already awaited coroutine
            await c;
        } catch(e){
            err_occurred = true;
        }
        return err_occurred;
        """
    )


def test_await_pyproxy_async_def(selenium):
    assert selenium.run_js(
        """
        let packages = await pyodide.runPython(`
            from js import fetch
            async def temp():
                return await (await fetch('packages.json')).json()
            temp()
        `);
        return (!!packages.dependencies) && (!!packages.import_name_to_package_name);
        """
    )
