🍣

Blocklyのカスタムブロックでasync/awaitを使って現在のBitcoinの価格を知りたい!

2021/05/16に公開

結論から

Image from Gyazo

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <script src="https://unpkg.com/blockly/blockly.min.js"></script>
  <script>
    Blockly.Blocks['console_log'] = {
      init: function () {
        this.appendDummyInput()
          .appendField(new Blockly.FieldTextInput("ログ"), "log")
          .appendField("出力");
        this.setPreviousStatement(true, null);
        this.setNextStatement(true, null);
        this.setColour(230);
        this.setTooltip("");
        this.setHelpUrl("");
      }
    };
    Blockly.JavaScript['console_log'] = function (block) {
      const text_log = block.getFieldValue('log');
      const code = 'console.log(\''+ text_log+'\');\n';
      return code;
    };

    Blockly.Blocks['wait'] = {
      init: function () {
        this.appendDummyInput()
          .appendField(new Blockly.FieldNumber(1), "sec")
          .appendField("秒待つ");
        this.setPreviousStatement(true, null);
        this.setNextStatement(true, null);
        this.setColour(230);
        this.setTooltip("");
        this.setHelpUrl("");
      }
    };
    Blockly.JavaScript['wait'] = function (block) {
      const number_sec = block.getFieldValue('sec');
      const code = 'await sleep('+ number_sec+');\n';
      return code;
    };
  </script>

  <title>Blockly test</title>
</head>

<body>
  <!-- Toolboxのブロック -->
  <xml xmlns="https://developers.google.com/blockly/xml" id="toolbox" style="display: none">
    <block type="math_number">
      <field name="NUM">123</field>
    </block>
    <block type="text"></block>
    <block type="text_print"></block>
    <block type="console_log"></block>
    <block type="wait"></block>
  </xml>

  <!-- Workspace -->
  <div id="blocklyDiv" style="height: 480px; width: 600px;"></div>

  <!-- 実行ボタン -->
  <button onclick="run()">実行</button>

  <script>
    const demoWorkspace = Blockly.inject('blocklyDiv',
      {
        media: 'https://unpkg.com/blockly/media/',
        toolbox: document.getElementById('toolbox')
      });

    function sleep(time) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve();
        }, time * 1000);
      });
    }

    function run() {
      const code = '(async () => {'+Blockly.JavaScript.workspaceToCode(demoWorkspace) + '})();';
      console.log(code);
      try {
        eval(code);
      } catch (e) {
        alert(e);
      }
    }
  </script>
</body>
</html>

大事な部分は

const code = '(async () => {'+Blockly.JavaScript.workspaceToCode(demoWorkspace) + '})();';

code生成したあとに、asyncで囲んで、即時実行しているところです。

カスタムブロックでは関数実行だけで、その関数は予め用意しておくとよいかなと思います。

せっかくなのでWebAPIを使って現在のビットコインの値段を調べてみよう

Image from Gyazo

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <script src="https://unpkg.com/blockly/blockly.min.js"></script>
  <script>
    Blockly.Blocks['console_log'] = {
      init: function () {
        this.appendDummyInput()
          .appendField(new Blockly.FieldTextInput("ログ"), "log")
          .appendField("出力");
        this.setPreviousStatement(true, null);
        this.setNextStatement(true, null);
        this.setColour(230);
        this.setTooltip("");
        this.setHelpUrl("");
      }
    };
    Blockly.JavaScript['console_log'] = function (block) {
      const text_log = block.getFieldValue('log');
      const code = 'console.log(\''+ text_log+'\');\n';
      return code;
    };

    Blockly.Blocks['wait'] = {
      init: function () {
        this.appendDummyInput()
          .appendField(new Blockly.FieldNumber(1), "sec")
          .appendField("秒待つ");
        this.setPreviousStatement(true, null);
        this.setNextStatement(true, null);
        this.setColour(230);
        this.setTooltip("");
        this.setHelpUrl("");
      }
    };
    Blockly.JavaScript['wait'] = function (block) {
      const number_sec = block.getFieldValue('sec');
      const code = 'await sleep('+ number_sec+');\n';
      return code;
    };

    Blockly.Blocks['webapi_btc'] = {
      init: function () {
        this.appendDummyInput()
          .appendField("現在のビットコインの価格(USD)");
        this.setOutput(true, null);
        this.setColour(230);
        this.setTooltip("");
        this.setHelpUrl("");
      }
    };
    Blockly.JavaScript['webapi_btc'] = function (block) {
      var code = 'await getBitcoinRate()';
      return [code, Blockly.JavaScript.ORDER_NONE];
    };
  </script>

  <title>Blockly test</title>
</head>

<body>
  <!-- Toolboxのブロック -->
  <xml xmlns="https://developers.google.com/blockly/xml" id="toolbox" style="display: none">
    <block type="math_number">
      <field name="NUM">123</field>
    </block>
    <block type="text"></block>
    <block type="text_print"></block>
    <block type="console_log"></block>
    <block type="wait"></block>
    <block type="webapi_btc"></block>
  </xml>

  <!-- Workspace -->
  <div id="blocklyDiv" style="height: 480px; width: 600px;"></div>

  <!-- 実行ボタン -->
  <button onclick="run()">実行</button>

  <!-- axiosライブラリ -->
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>

  <script>
    const demoWorkspace = Blockly.inject('blocklyDiv',
      {
        media: 'https://unpkg.com/blockly/media/',
        toolbox: document.getElementById('toolbox')
      });

    function sleep(time) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve();
        }, time * 1000);
      });
    }

    // USD
    async function getBitcoinRate() {
      // axiosでBTCPriceのAPIを叩きます(少し時間がかかる)
      const res = await axios.get('https://api.coindesk.com/v1/bpi/currentprice.json');
      // 取得できたBTCのUSD価格を抜き出す
      return res.data.bpi.USD.rate_float;
    }

    function run() {
      const code = '(async () => {'+Blockly.JavaScript.workspaceToCode(demoWorkspace) + '})();';
      console.log(code);
      try {
        eval(code);
      } catch (e) {
        alert(e);
      }
    }
  </script>
</body>
</html>

注意点

codeを文字列で返すとき、最後セミコロン(;)があるとアラート出力するときに、 alert(await getBitcoinRate();); となってしまってシンタックスエラーになってしまいます。

ここらへん全部対応できないことあるかもなぁ

Discussion