// addTopLevelExpression generates the source for the given expression and adds it to the // state machine. func (sg *stateGenerator) addTopLevelExpression(expression codedom.Expression) esbuilder.SourceBuilder { // Generate the expression. result := expressiongenerator.GenerateExpression(expression, expressiongenerator.AllowedSync, sg.scopegraph, sg.positionMapper, sg.generateMachine) // Add any variables generated by the expression. for _, varName := range result.Variables() { sg.addVariable(varName) } // If the expression generated is asynchronous, then it will have at least one // promise callback. In order to ensure continued execution, we have to wrap // the resulting expression in a jump to a new state once it has finished executing. if result.IsAsync() { // Add $result, as it is needed below. sg.addVariable("$result") sg.hasAsyncJump = true // Save the current state and create a new state to jump to once the expression's // promise has resolved. currentState := sg.currentState targetState := sg.newState() // Wrap the expression's promise result expression to be stored in $result, // followed by a jump to the new state. data := struct { ResolutionStateId stateId Snippets snippets IsGenerator bool }{targetState.ID, sg.snippets(), sg.isGeneratorFunction} wrappingTemplateStr := ` $result = {{ emit .ResultExpr }}; {{ .Data.Snippets.SetState .Data.ResolutionStateId }} {{ .Data.Snippets.Continue .Data.IsGenerator }} ` promise := result.BuildWrapped(wrappingTemplateStr, data) // Wrap the expression's promise with a catch, in case it fails. catchTemplateStr := ` ({{ emit .Item }}).catch(function(err) { {{ .Snippets.Reject "err" }} }); return; ` currentState.pushBuilt(esbuilder.Template("tlecatch", catchTemplateStr, generatingItem{promise, sg})) return esbuilder.Identifier("$result") } else { // Otherwise, the expression is synchronous and we can just invoke it. return result.Build() } }
// generateResolveExpression generates the code for an expression resolution. func (sg *stateGenerator) generateResolveExpression(resolveExpression *codedom.ResolveExpressionNode) { // Generate the resolved expression, requiring that it is asynchronous to ensure it becomes // a Promise. result := expressiongenerator.GenerateExpression(resolveExpression.ChildExpression, expressiongenerator.EnsureAsync, sg.scopegraph, sg.positionMapper, sg.generateMachine) var resolutionName = "" var rejectionName = "" if resolveExpression.ResolutionName != "" { resolutionName = sg.addVariable(resolveExpression.ResolutionName) } if resolveExpression.RejectionName != "" { rejectionName = sg.addVariable(resolveExpression.RejectionName) } // Save the current state and create a new state to jump to once the expression's // promise has resolved or rejected. currentState := sg.currentState sg.generateStates(resolveExpression.Target, generateNewState) // Build the expression with an assignment of the resolved expression value assigned to // the resolution variable (if any) and then a jump to the post-resolution state. jumpToTarget := sg.jumpToStatement(resolveExpression.Target) resolveData := struct { ResolutionName string RejectionName string JumpToTarget string Snippets snippets IsGenerator bool }{resolutionName, rejectionName, jumpToTarget, sg.snippets(), sg.isGeneratorFunction} wrappingTemplateStr := ` {{ if .Data.ResolutionName }} {{ .Data.ResolutionName }} = {{ emit .ResultExpr }}; {{ end }} {{ if .Data.RejectionName }} {{ .Data.RejectionName }} = null; {{ end }} {{ .Data.JumpToTarget }} {{ .Data.Snippets.Continue .Data.IsGenerator }} ` promise := result.BuildWrapped(wrappingTemplateStr, resolveData) // Similarly, add a .catch onto the Promise with an assignment of the rejected error (if any) // to the rejection variable (if any) and then a jump to the post-rejection state. rejectData := struct { Promise esbuilder.SourceBuilder ResolutionName string RejectionName string JumpToTarget string Snippets snippets IsGenerator bool }{promise, resolutionName, rejectionName, jumpToTarget, sg.snippets(), sg.isGeneratorFunction} catchTemplateStr := ` ({{ emit .Promise }}).catch(function($rejected) { {{ if .RejectionName }} {{ .RejectionName }} = $rejected; {{ end }} {{ if .ResolutionName }} {{ .ResolutionName }} = null; {{ end }} {{ .JumpToTarget }} {{ .Snippets.Continue .IsGenerator }} }); return; ` currentState.pushBuilt(esbuilder.Template("resolvecatch", catchTemplateStr, rejectData)) }