Skip to content

Commit

Permalink
Promise.resolve & doc
Browse files Browse the repository at this point in the history
  • Loading branch information
FurryR committed Jan 25, 2024
1 parent d7d3863 commit 8fd46f5
Show file tree
Hide file tree
Showing 11 changed files with 156 additions and 46 deletions.
72 changes: 55 additions & 17 deletions doc/definition/builtin.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,33 +68,71 @@ declare class Function {
Here is the definition of `Promise` class.

```typescript
declare class Promise {
interface Thenable<T> {
then(onFulfilled?: (value: T) => void, onRejected?: (reason: any) => void): void
}
interface PromiseLike<T> implements Thenable<T> {
/**
* Attaches callbacks for the resolution and/or rejection of the Promise.
* @param onFulfilled The callback to execute when the Promise is resolved.
* @param onRejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of which ever callback is executed.
*/
then<TResult1 = T, TResult2 = never>(onFulfilled?: (value: T) => TResult1 | PromiseLike<TResult1>, onRejected?: (reason: any) => TResult2 | PromiseLike<TResult2>): PromiseLike<TResult1 | TResult2>
}
/**
* Recursively unwraps the "awaited type" of a type. Non-promise "thenables" should resolve to `never`. This emulates the behavior of `await`.
*/
type Awaited<T> = T extends null ? T : // special case for `null | undefined` when not in `--strictNullChecks` mode
T extends object & { then(onfulfilled: infer F, ...args: infer _): any; } ? // `await` only unwraps object types with a callable `then`. Non-object types are not unwrapped
F extends ((value: infer V, ...args: infer _) => any) ? // if the argument to `then` is callable, extracts the first argument
Awaited<V> : // recursively unwrap the value
never : // the argument to `then` was not callable
T // non-object or non-thenable
declare class Promise<T> implements PromiseLike<T> {
/**
* @constructor Construct a Promise instance.
* @param executor Executor.
*/
constructor(
executor: (
resolve: (value: any | PromiseLike<any>) => void,
reject: (reason: any) => void
) => void
executor: (resolve: (value: T | Thenable<T>) => void, reject: (reason?: any) => void) => void
)
/**
* Register then functions.
* @param onFulfilled Triggers when the promise is fulfilled.
* @param onRejected Triggers when the promise is rejected.
* @returns New Promise instance.
* Attaches callbacks for the resolution and/or rejection of the Promise.
* @param onFulfilled The callback to execute when the Promise is resolved.
* @param onRejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of which ever callback is executed.
*/
then<TResult1 = T, TResult2 = never>(onFulfilled?: ((value: T) => TResult1 | Thenable<TResult1>), onRejected?: (reason: any) => TResult2 | Thenable<TResult2>): Promise<TResult1 | TResult2>
/**
* Attaches a callback for only the rejection of the Promise.
* @param onRejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of the callback.
*/
catch<TResult = never>(onRejected?: ((reason: any) => TResult | Thenable<TResult>)): Promise<T | TResult>;
/**
* Creates a new rejected promise for the provided reason.
* @param reason The reason the promise was rejected.
* @returns A new rejected Promise.
*/
static reject<T = never>(reason?: any): Promise<T>
/**
* Creates a new resolved promise.
* @returns A resolved promise.
*/
static resolve(): Promise<void>
/**
* Creates a new resolved promise for the provided value.
* @param value A promise.
* @returns A promise whose internal state matches the provided promise.
*/
then(
onFulfilled: (value: any) => any | PromiseLike<any>,
onRejected: (reason: any) => any | PromiseLike<any>
): Promise<any>
static resolve<T>(value: T): Promise<Awaited<T>>
/**
* Register exception handlers.
* @param onRejected Triggers when the promise is rejected.
* @returns New Promise instance.
* Creates a new resolved promise for the provided value.
* @param value A promise.
* @returns A promise whose internal state matches the provided promise.
*/
catch(onRejected: (reason: any) => any): Promise<any>
static resolve<T>(value: T | Thenable<T>): Promise<Awaited<T>>;
}
```

Expand Down
75 changes: 63 additions & 12 deletions src/core/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import {
serializeObject,
ensureValue,
deserializeObject,
isPromise
isPromise,
processThenReturn
} from './helper'
function processPromise(self: LppPromise, res: LppPromise): LppReturn {
self.pm = res.pm
Expand Down Expand Up @@ -235,9 +236,15 @@ export namespace Global {
const temp = LppPromise.generate((resolve, reject) => {
const res = fn.apply(self, [
new LppFunction((_, args) => {
const v = args[0] ?? new LppConstant(null)
// TODO: detect if v is PromiseLike
resolve(v)
// resolve
const res = processThenReturn(
new LppReturn(args[0] ?? new LppConstant(null)),
resolve,
reject
)
if (isPromise(res)) {
return res.then(() => new LppReturn(new LppConstant(null)))
}
return new LppReturn(new LppConstant(null))
}),
new LppFunction((_, args) => {
Expand Down Expand Up @@ -266,14 +273,10 @@ export namespace Global {
[
'then',
LppFunction.native((self, args) => {
if (
self instanceof LppPromise &&
args.length > 0 &&
args[0] instanceof LppFunction
) {
if (self instanceof LppPromise) {
return new LppReturn(
self.done(
args[0],
args[0] instanceof LppFunction ? args[0] : undefined,
args[1] instanceof LppFunction ? args[1] : undefined
)
)
Expand Down Expand Up @@ -311,6 +314,54 @@ export namespace Global {
])
)
)
Promise.set(
'resolve',
LppFunction.native((self, args) => {
if (self !== Promise) {
const res = IllegalInvocationError.construct([])
if (isPromise(res))
throw new globalThis.Error(
'lpp: IllegalInvocationError constructor should be synchronous'
)
if (res instanceof LppException) return res
return new LppException(res.value)
}
const res = LppPromise.generate((resolve, reject) => {
const res = processThenReturn(
new LppReturn(args[0] ?? new LppConstant(null)),
resolve,
reject
)
if (isPromise(res)) {
return res.then(() => {})
}
return undefined
})
if (isPromise(res)) {
return res.then(v => new LppReturn(v))
}
return new LppReturn(res)
})
)
Promise.set(
'reject',
LppFunction.native((self, args) => {
if (self !== Promise) {
const res = IllegalInvocationError.construct([])
if (isPromise(res))
throw new globalThis.Error(
'lpp: IllegalInvocationError constructor should be synchronous'
)
if (res instanceof LppException) return res
return new LppException(res.value)
}
return new LppReturn(
new LppPromise(
globalThis.Promise.reject(args[0] ?? new LppConstant(null))
)
)
})
)
/**
* lpp builtin `Error` -- `Error` objects are thrown when runtime errors occur.
*/
Expand Down Expand Up @@ -384,7 +435,7 @@ export namespace Global {
[
'parse',
LppFunction.native((self, args) => {
if (self != JSON) {
if (self !== JSON) {
const res = IllegalInvocationError.construct([])
if (isPromise(res))
throw new globalThis.Error(
Expand Down Expand Up @@ -426,7 +477,7 @@ export namespace Global {
[
'stringify',
LppFunction.native((self, args) => {
if (self != JSON) {
if (self !== JSON) {
const res = IllegalInvocationError.construct([])
if (isPromise(res))
throw new globalThis.Error(
Expand Down
22 changes: 12 additions & 10 deletions src/core/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1087,19 +1087,21 @@ export class LppPromise extends LppObject {
* @param rejectFn
* @returns
*/
done(resolveFn: LppFunction, rejectFn?: LppFunction): LppPromise {
done(resolveFn?: LppFunction, rejectFn?: LppFunction): LppPromise {
return LppPromise.generate((resolve, reject) => {
this.pm.then(
value => {
if (value instanceof LppValue) {
const res = resolveFn.apply(this, [value])
if (isPromise(res)) {
return res.then(v => processThenReturn(v, resolve, reject))
resolveFn
? value => {
if (value instanceof LppValue) {
const res = resolveFn.apply(this, [value])
if (isPromise(res)) {
return res.then(v => processThenReturn(v, resolve, reject))
}
return processThenReturn(res, resolve, reject)
}
throw new Error('lpp: unknown result')
}
return processThenReturn(res, resolve, reject)
}
throw new Error('lpp: unknown result')
},
: undefined,
rejectFn
? err => {
if (err instanceof LppValue) {
Expand Down
4 changes: 3 additions & 1 deletion src/impl/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ export function attachType() {
attachType(Global.Function.get('serialize'), ['fn'])
attachType(Global.Promise, ['executor'])
attachType(Global.Promise.get('prototype').get('then'), [
'onFulfilled',
'onFulfilled?',
'onRejected?'
])
attachType(Global.Promise.get('resolve'), ['value?'])
attachType(Global.Promise.get('reject'), ['reason?'])
attachType(Global.Promise.get('prototype').get('catch'), ['onRejected'])
attachType(Global.JSON.get('parse'), ['json'])
attachType(Global.JSON.get('stringify'), ['value'])
Expand Down
17 changes: 17 additions & 0 deletions src/impl/promise.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// TODO: a PromiseLike object that **does not meet the requirement of A+ standard**, used for performance (and compatibility with Scratch).
// Reference (https://promisesaplus.com/):
// 2.2.4 onFulfilled or onRejected must not be called until the execution context stack contains only platform code. [3.1].
// export class ImmediatePromise<T> implements PromiseLike<T> {
// /**
// * Attaches callbacks for the resolution and/or rejection of the Promise.
// * @param onFulfilled The callback to execute when the Promise is resolved.
// * @param onRejected The callback to execute when the Promise is rejected.
// * @returns A Promise for the completion of which ever callback is executed.
// */
// then<TResult1 = T, TResult2 = never>(
// onFulfilled?: (value: T) => TResult1 | PromiseLike<TResult1>,
// onRejected?: (reason: unknown) => TResult2 | PromiseLike<TResult2>
// ): PromiseLike<TResult1 | TResult2> {
// throw new Error('not implemented')
// }
// }
2 changes: 1 addition & 1 deletion src/impl/serialization/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type VM from 'scratch-vm'
import type * as VM from 'scratch-vm'
import { LppFunction } from 'src/core'
export interface SerializationInfo {
/**
Expand Down
2 changes: 1 addition & 1 deletion src/impl/traceback/dialog.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type ScratchBlocks from 'blockly/core'
import type * as ScratchBlocks from 'blockly/core'
import { BlocklyInstance } from '../blockly'

/**
Expand Down
2 changes: 1 addition & 1 deletion src/impl/traceback/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { VM } from '../typing'
import { LppException } from 'src/core'
import { BlocklyInstance } from '../blockly'
import * as Dialog from './dialog'
import type ScratchBlocks from 'blockly/core'
import type * as ScratchBlocks from 'blockly/core'
import { LppTraceback } from '../context'
import { Inspector } from './inspector'
let lastNotification: Notification | undefined
Expand Down
2 changes: 1 addition & 1 deletion src/impl/traceback/inspector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { Dialog } from '.'
import { BlocklyInstance } from '../blockly'
import { hasMetadata } from '../serialization'
import type { VM } from '../typing'
import type ScratchBlocks from 'blockly/core'
import type * as ScratchBlocks from 'blockly/core'

/**
* Generate an inspector of specified object.
Expand Down
2 changes: 1 addition & 1 deletion src/impl/typing/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type OriginalVM from 'scratch-vm'
import type * as OriginalVM from 'scratch-vm'
import type { Message } from 'format-message'
import type {
LppArray,
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
/* Module Resolution Options */
// "moduleResolution": "",
// "esModuleInterop": true,
"allowSyntheticDefaultImports": true,
// "allowSyntheticDefaultImports": true,
"types": [],
"baseUrl": ".",
"paths": {
Expand Down

0 comments on commit 8fd46f5

Please sign in to comment.