Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Define a method to be an async function #426

Open
MrWindlike opened this issue May 31, 2022 · 3 comments
Open

Proposal: Define a method to be an async function #426

MrWindlike opened this issue May 31, 2022 · 3 comments
Labels
feature New feature or request runtime about meta-ui runtime

Comments

@MrWindlike
Copy link
Contributor

The Problem

Currently, if we want to call the method after the fetch trait is complete or error, we need to add all the handlers into the fetch trait’s onComplete and use the disabled to control whether they should be executed. In addition, we also need to add the global states to mark the different trigger ways and clear their values after using it which brings a lot of mental burdens.

For example, I want to implement this demand:

  • when click button1: call api → set state value →set input1's value to button1 after complete
  • when click button2: call api → set state value → set input1's value to button2 after complete

The whole application schema to be this:

{
  "version": "sunmao/v1",
  "kind": "Application",
  "metadata": {
    "name": "some App"
  },
  "spec": {
    "components": [
      {
        "id": "api",
        "type": "core/v1/dummy",
        "properties": {},
        "traits": [
          {
            "type": "core/v1/fetch",
            "properties": {
              "url": "",
              "method": "get",
              "lazy": true,
              "disabled": false,
              "headers": {},
              "body": "{{[]}}",
              "bodyType": "json",
              "onComplete": [
                {
                  "componentId": "state",
                  "method": {
                    "name": "setValue",
                    "parameters": {
                      "key": "value",
                      "value": "{{api.fetch.data}}"
                    }
                  },
                  "disabled": false,
                  "wait": {
                    "type": "delay",
                    "time": 0
                  }
                },
                {
                  "componentId": "input1",
                  "method": {
                    "name": "setInputValue",
                    "parameters": {
                      "value": "button1"
                    }
                  },
                  "disabled": "{{!isClickButton1.value}}",
                  "wait": {
                    "type": "delay",
                    "time": 0
                  }
                },
                {
                  "componentId": "input1",
                  "method": {
                    "name": "setInputValue",
                    "parameters": {
                      "value": "button2"
                    }
                  },
                  "disabled": "{{!isClickButton2.value}}",
                  "wait": {
                    "type": "delay",
                    "time": 0
                  }
                },
                {
                  "componentId": "isClickButton1",
                  "method": {
                    "name": "setValue",
                    "parameters": {
                      "key": "value",
                      "value": "{{false}}"
                    }
                  },
                  "disabled": false,
                  "wait": {
                    "type": "delay",
                    "time": 0
                  }
                },
                {
                  "componentId": "isClickButton2",
                  "method": {
                    "name": "setValue",
                    "parameters": {
                      "key": "value",
                      "value": "{{false}}"
                    }
                  },
                  "disabled": false,
                  "wait": {
                    "type": "delay",
                    "time": 0
                  }
                }
              ],
              "onError": [
                {
                  "componentId": "isClickButton1",
                  "method": {
                    "name": "setValue",
                    "parameters": {
                      "key": "value",
                      "value": "{{false}}"
                    }
                  },
                  "disabled": false,
                  "wait": {
                    "type": "delay",
                    "time": 0
                  }
                },
                {
                  "componentId": "isClickButton2",
                  "method": {
                    "name": "setValue",
                    "parameters": {
                      "key": "value",
                      "value": "{{false}}"
                    }
                  },
                  "disabled": false,
                  "wait": {
                    "type": "delay",
                    "time": 0
                  }
                }
              ]
            }
          }
        ]
      },
      {
        "id": "isClickButton1",
        "type": "core/v1/dummy",
        "properties": {},
        "traits": [
          {
            "type": "core/v1/state",
            "properties": {
              "key": "value",
              "initialValue": "{{false}}"
            }
          }
        ]
      },
      {
        "id": "isClickButton2",
        "type": "core/v1/dummy",
        "properties": {},
        "traits": [
          {
            "type": "core/v1/state",
            "properties": {
              "key": "value",
              "initialValue": "{{false}}"
            }
          }
        ]
      },
      {
        "id": "input1",
        "type": "chakra_ui/v1/input",
        "properties": {
          "variant": "outline",
          "placeholder": "Please input value",
          "size": "md",
          "focusBorderColor": "",
          "isDisabled": false,
          "isRequired": false,
          "defaultValue": ""
        },
        "traits": []
      },
      {
        "id": "button1",
        "type": "arco/v1/button",
        "properties": {
          "type": "default",
          "status": "default",
          "long": false,
          "size": "default",
          "disabled": false,
          "loading": false,
          "shape": "square",
          "text": "button1"
        },
        "traits": [
          {
            "type": "core/v1/event",
            "properties": {
              "handlers": [
                {
                  "type": "onClick",
                  "componentId": "isClickButton1",
                  "method": {
                    "name": "setValue",
                    "parameters": {
                      "key": "value",
                      "value": "{{true}}"
                    }
                  },
                  "disabled": false,
                  "wait": {
                    "type": "delay",
                    "time": 0
                  }
                },
                {
                  "type": "onClick",
                  "componentId": "api",
                  "method": {
                    "name": "triggerFetch",
                    "parameters": {}
                  },
                  "disabled": false,
                  "wait": {
                    "type": "delay",
                    "time": 0
                  }
                }
              ]
            }
          }
        ]
      },
      {
        "id": "button2",
        "type": "arco/v1/button",
        "properties": {
          "type": "default",
          "status": "default",
          "long": false,
          "size": "default",
          "disabled": false,
          "loading": false,
          "shape": "square",
          "text": "button2"
        },
        "traits": [
          {
            "type": "core/v1/event",
            "properties": {
              "handlers": [
                {
                  "type": "onClick",
                  "componentId": "isClickButton2",
                  "method": {
                    "name": "setValue",
                    "parameters": {
                      "key": "value",
                      "value": "{{true}}"
                    }
                  },
                  "disabled": false,
                  "wait": {
                    "type": "delay",
                    "time": 0
                  }
                },
                {
                  "type": "onClick",
                  "componentId": "api",
                  "method": {
                    "name": "triggerFetch",
                    "parameters": {}
                  },
                  "disabled": false,
                  "wait": {
                    "type": "delay",
                    "time": 0
                  }
                }
              ]
            }
          }
        ]
      },
      {
        "id": "state",
        "type": "core/v1/dummy",
        "properties": {},
        "traits": [
          {
            "type": "core/v1/state",
            "properties": {
              "key": "value"
            }
          }
        ]
      }
    ]
  }
}

The logic turn to JavaScript code just like this:

function trigger () {
  api().then(()=> {
    setState()
    if (isClickButton1) {
      setInput1('button1')
    }
    if (isClickButton2) {
      setInput1('button2')
    }
    setIsClickButton1(false)
    setIsClickButton2(false)
  }).catch(()=> {
    setIsClickButton1(false)
    setIsClickButton2(false)
  })
}

function handleClickButton1 () {
  setIsClickButton1(true)
  trigger()
}

function handleClickButton2 () {
  setIsClickButton2(true)
  trigger()
}

🙃  There are so awful to do this.

Improvement

To improve this, I want to define whether the method is the async function.

The spec to define a method to be the async function may be like this:

{
  methods: [
    {
      name: 'triggerFetch',
      parameters: Type.Object({}),
      async: true
    },
  ],
}

Then if the method is an async function, it would support the onComplete and onError in the schema to call other methods.

// packages/runtime/src/components/_internal/ImplWrapper/hooks/useGlobalHandlerMap.ts
const handler = async (schema) => {
  const method = handlerMap[schema.name];

  if (method.async) {
    try {
      await method(schema.parameters);
      c.onComplete.forEach(onCompleteschema=> generateCallback(onCompleteschema)());
    } catch {
      c.onError.forEach(onErrorSchema=> generateCallback(onErrorSchema)());
    }
  } else {
    method(s.parameters);
  }
};
// packages/runtime/src/traits/core/Fetch.tsx
export default implementRuntimeTrait({
  version: CORE_VERSION,
  metadata: {
    name: CoreTraitName.Fetch,
    description: 'fetch data to store',
  },
  ...
})(()=> {
  ...

  const fetchData = () => {
    ...
    return fetch();
  };

  subscribeMethods({
      triggerFetch() {
        return fetchData();
      },
      ...
  });

  ...
})

The whole schema may be like this:

{
  "version": "sunmao/v1",
  "kind": "Application",
  "metadata": {
    "name": "some App"
  },
  "spec": {
    "components": [
      {
        "id": "api",
        "type": "core/v1/dummy",
        "properties": {},
        "traits": [
          {
            "type": "core/v1/fetch",
            "properties": {
              "url": "",
              "method": "get",
              "lazy": true,
              "disabled": false,
              "headers": {},
              "body": "{{[]}}",
              "bodyType": "json",
              "onComplete": [
                {
                  "componentId": "state",
                  "method": {
                    "name": "setValue",
                    "parameters": {
                      "key": "value",
                      "value": "{{api.fetch.data}}"
                    }
                  },
                  "disabled": false,
                  "wait": {
                    "type": "delay",
                    "time": 0
                  }
                }
              ],
              "onError": []
            }
          }
        ]
      },
      {
        "id": "input1",
        "type": "chakra_ui/v1/input",
        "properties": {
          "variant": "outline",
          "placeholder": "Please input value",
          "size": "md",
          "focusBorderColor": "",
          "isDisabled": false,
          "isRequired": false,
          "defaultValue": ""
        },
        "traits": []
      },
      {
        "id": "button1",
        "type": "arco/v1/button",
        "properties": {
          "type": "default",
          "status": "default",
          "long": false,
          "size": "default",
          "disabled": false,
          "loading": false,
          "shape": "square",
          "text": "button1"
        },
        "traits": [
          {
            "type": "core/v1/event",
            "properties": {
              "handlers": [
                {
                  "type": "onClick",
                  "componentId": "api",
                  "method": {
                    "name": "triggerFetch",
                    "parameters": {},
+                   "onComplete": [
+                     {
+                       "componentId": "input1",
+                       "method": {
+                         "name": "setInputValue",
+                         "parameters": {
+                           "key": "value",
+                           "value": "button1"
+                         }
+                       },
+                       "disabled": false,
+                       "wait": {
+                         "type": "delay",
+                         "time": 0
+                       }
+                     }
+                   ]
                  },
                  "disabled": false,
                  "wait": {
                    "type": "delay",
                    "time": 0
                  }
                }
              ]
            }
          }
        ]
      },
      {
        "id": "button2",
        "type": "arco/v1/button",
        "properties": {
          "type": "default",
          "status": "default",
          "long": false,
          "size": "default",
          "disabled": false,
          "loading": false,
          "shape": "square",
          "text": "button2"
        },
        "traits": [
          {
            "type": "core/v1/event",
            "properties": {
              "handlers": [
                {
                  "type": "onClick",
                  "componentId": "api",
                  "method": {
                    "name": "triggerFetch",
                    "parameters": {},
+                   "onComplete": [
+                     {
+                       "componentId": "input1",
+                       "method": {
+                         "name": "setInputValue",
+                         "parameters": {
+                           "key": "value",
+                           "value": "button2"
+                         }
+                       },
+                       "disabled": false,
+                       "wait": {
+                         "type": "delay",
+                         "time": 0
+                       }
+                     }
+                   ]
                  },
                  "disabled": false,
                  "wait": {
                    "type": "delay",
                    "time": 0
                  }
                }
              ]
            }
          }
        ]
      },
      {
        "id": "state",
        "type": "core/v1/dummy",
        "properties": {},
        "traits": [
          {
            "type": "core/v1/state",
            "properties": {
              "key": "value"
            }
          }
        ]
      }
    ]
  }
}

The logic turn to JavaScript code just like this:

async function trigger () {
  await api()
  setState()
}

function handleClickButton1 () {
  trigger().then(()=> setInput1('button1'))
}

function handleClickButton2 () {
  trigger().then(()=> setInput1('button2'))
}

Compare that to the before schema:

  • Remove the state isClickButton1, isClickButton2
  • Move the onComplete from the fetch trait to the button1 and button2
@MrWindlike MrWindlike added feature New feature or request runtime about meta-ui runtime labels May 31, 2022
@Yuyz0112
Copy link
Contributor

Yuyz0112 commented May 31, 2022

function trigger () {
  api().then(()=> {
    setState()
    if (isClickButton1) {
      setInput1('button1')
    }
    if (isClickButton2) {
      setInput1('button2')
    }
    setIsClickButton1(false)
    setIsClickButton2(false)
  }).catch(()=> {
    setIsClickButton1(false)
    setIsClickButton2(false)
  })
}

function handleClickButton1 () {
  setIsClickButton1(true)
  trigger()
}

function handleClickButton2 () {
  setIsClickButton2(true)
  trigger()
}

Why do these two buttons share the same trigger, instead of

function trigger1 () {
  api().then(()=> {
    setInput1('button1')
  })
}

function trigger2 () {
  api().then(()=> {
    setInput2('button2')
  })
}

function handleClickButton1 () {
  trigger1()
}

function handleClickButton2 () {
  trigger2()
}

@MrWindlike
Copy link
Contributor Author

Why do these two buttons share the same trigger

Because they use the same fetch trait to call the same API, they use the same trigger. However, the things the buttons need to do in onComplete is different.

Do you mean to use the different fetch traits to call the same API and then they can use the different onComplete to solve the problem?

@MrWindlike
Copy link
Contributor Author

They can't reuse the parameters and some common logic and so on if use the different fetch traits to call the same API. I think this isn't the best way to solve the problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature or request runtime about meta-ui runtime
Projects
None yet
Development

No branches or pull requests

2 participants