• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

source-academy / js-slang / 6107141959

07 Sep 2023 08:11AM UTC coverage: 82.853% (+0.2%) from 82.683%
6107141959

push

github

web-flow
Use Asynchronous Code for Loading Modules (#1471)

* Add async loading code

* Made infinite loop checking async

* Update stepper to be async

* Add assert and misc utils

* Update transpiler to be async

* Update runners

* Add new import options

* Ignore tests during typechecking

* Relocate typeguards

* Update with assertions and typeguards

* Update repl transpiler to be async

* Abstract out module context initialization and tab loading

* Renamed promise timeout error and added tests

* Remove old code

* Update options

* Add files to avoid line ending issues

* Use POSIX paths on Windows systems

* Ran format

* Update eslint rules to follow exclusion of tests

* Incorporate changes for posix path handling

* Minor change to resolve certain webpack issues with the frontend

* Use a different posix path import

* Allow loading of export default tabs

* Refactor collation of import declarations

---------

Co-authored-by: Ian Yong <ianyongyc@gmail.com>

3629 of 4814 branches covered (0.0%)

Branch coverage included in aggregate %.

266 of 266 new or added lines in 25 files covered. (100.0%)

10886 of 12705 relevant lines covered (85.68%)

96881.51 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

73.24
/src/modules/moduleLoaderAsync.ts
1
import type { Node } from 'estree'
2
import { memoize } from 'lodash'
65✔
3

4
import type { Context } from '..'
5
import { PromiseTimeoutError, timeoutPromise } from '../utils/misc'
65✔
6
import { wrapSourceModule } from '../utils/operators'
65✔
7
import { ModuleConnectionError, ModuleInternalError, ModuleNotFoundError } from './errors'
65✔
8
import { MODULES_STATIC_URL } from './moduleLoader'
65✔
9
import type { ModuleBundle, ModuleDocumentation, ModuleManifest } from './moduleTypes'
10
import { getRequireProvider } from './requireProvider'
65✔
11
import { evalRawTab } from './utils'
65✔
12

13
export function httpGetAsync(path: string, type: 'json'): Promise<object>
14
export function httpGetAsync(path: string, type: 'text'): Promise<string>
15
export async function httpGetAsync(path: string, type: 'json' | 'text') {
65✔
16
  try {
15✔
17
    const resp = await timeoutPromise(
15✔
18
      fetch(path, {
19
        method: 'GET'
20
      }),
21
      10000
22
    )
23

24
    if (resp.status !== 200 && resp.status !== 304) {
14✔
25
      throw new ModuleConnectionError()
1✔
26
    }
27

28
    const promise = type === 'text' ? resp.text() : resp.json()
13✔
29
    return timeoutPromise(promise, 10000)
13✔
30
  } catch (error) {
31
    if (error instanceof TypeError || error instanceof PromiseTimeoutError) {
2✔
32
      throw new ModuleConnectionError()
1✔
33
    }
34
    if (!(error instanceof ModuleConnectionError)) throw new ModuleInternalError(path, error)
1!
35
    throw error
1✔
36
  }
37
}
38

39
/**
40
 * Send a HTTP GET request to the modules endpoint to retrieve the manifest
41
 * @return Modules
42
 */
43
export const memoizedGetModuleManifestAsync = memoize(getModuleManifestAsync)
65✔
44
function getModuleManifestAsync(): Promise<ModuleManifest> {
45
  return httpGetAsync(`${MODULES_STATIC_URL}/modules.json`, 'json') as Promise<ModuleManifest>
3✔
46
}
47

48
async function checkModuleExists(moduleName: string, node?: Node) {
49
  const modules = await memoizedGetModuleManifestAsync()
2✔
50
  // Check if the module exists
51
  if (!(moduleName in modules)) throw new ModuleNotFoundError(moduleName, node)
2!
52

53
  return modules[moduleName]
2✔
54
}
55

56
export const memoizedGetModuleBundleAsync = memoize(getModuleBundleAsync)
65✔
57
async function getModuleBundleAsync(moduleName: string): Promise<string> {
58
  return httpGetAsync(`${MODULES_STATIC_URL}/bundles/${moduleName}.js`, 'text')
3✔
59
}
60

61
export const memoizedGetModuleTabAsync = memoize(getModuleTabAsync)
65✔
62
function getModuleTabAsync(tabName: string): Promise<string> {
63
  return httpGetAsync(`${MODULES_STATIC_URL}/tabs/${tabName}.js`, 'text')
5✔
64
}
65

66
export const memoizedGetModuleDocsAsync = memoize(getModuleDocsAsync)
65✔
67
async function getModuleDocsAsync(moduleName: string): Promise<ModuleDocumentation | null> {
68
  try {
×
69
    const result = await httpGetAsync(`${MODULES_STATIC_URL}/jsons/${moduleName}.json`, 'json')
×
70
    return result as ModuleDocumentation
×
71
  } catch (error) {
72
    console.warn(`Failed to load documentation for ${moduleName}:`, error)
×
73
    return null
×
74
  }
75
}
76

77
export async function loadModuleTabsAsync(moduleName: string, node?: Node) {
65✔
78
  const moduleInfo = await checkModuleExists(moduleName, node)
2✔
79

80
  // Load the tabs for the current module
81
  return Promise.all(
2✔
82
    moduleInfo.tabs.map(async path => {
4✔
83
      const rawTabFile = await memoizedGetModuleTabAsync(path)
4✔
84
      try {
4✔
85
        return evalRawTab(rawTabFile)
4✔
86
      } catch (error) {
87
        // console.error('tab error:', error);
88
        throw new ModuleInternalError(path, error, node)
2✔
89
      }
90
    })
91
  )
92
}
93

94
export async function loadModuleBundleAsync(
65✔
95
  moduleName: string,
96
  context: Context,
97
  wrapModule: boolean,
98
  node?: Node
99
) {
100
  // await checkModuleExists(moduleName, node)
101
  const moduleText = await memoizedGetModuleBundleAsync(moduleName)
2✔
102
  try {
2✔
103
    const moduleBundle: ModuleBundle = eval(moduleText)
2✔
104

105
    if (wrapModule) return wrapSourceModule(moduleName, moduleBundle, getRequireProvider(context))
1!
106
    return moduleBundle(getRequireProvider(context))
1✔
107
  } catch (error) {
108
    // console.error("bundle error: ", error)
109
    throw new ModuleInternalError(moduleName, error, node)
1✔
110
  }
111
}
112

113
/**
114
 * Initialize module contexts and add UI tabs needed for modules to program context
115
 */
116
export async function initModuleContextAsync(
65✔
117
  moduleName: string,
118
  context: Context,
119
  loadTabs: boolean
120
) {
121
  // Load the module's tabs
122
  if (!(moduleName in context.moduleContexts)) {
×
123
    context.moduleContexts[moduleName] = {
×
124
      state: null,
125
      tabs: loadTabs ? await loadModuleTabsAsync(moduleName) : null
×
126
    }
127
  } else if (context.moduleContexts[moduleName].tabs === null && loadTabs) {
×
128
    context.moduleContexts[moduleName].tabs = await loadModuleTabsAsync(moduleName)
×
129
  }
130
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc