Используйте переменную из обратного вызова в качестве глобальной переменной

Я сделал блок, в котором происходит запрос на указанный URL.

Внутри этого блока я могу работать с данными из ответа, но вне этого блока я не могу получить эти данные из-за асинхронности.

Можно ли имитировать синхронный запрос блочно или каким-либо другим способом, сохранять полученные данные в глобальной переменной?

введите описание изображения здесь

код созданного блока:

Blockly.Blocks['request'] =
'<block type="request">'
+ '     <value name="URL">'
+ '         <shadow type="text">'
+ '             <field name="TEXT">text</field>'
+ '         </shadow>'
+ '     </value>'
+ '     <value name="LOG">'
+ '     </value>'
+ '     <value name="WITH_STATEMENT">'
+ '     </value>'
+ '     <mutation with_statement="false"></mutation>'
+ '</block>';

Blockly.Blocks['request'] = {
init: function() {
    this.appendDummyInput('TEXT')
        .appendField('request');

    this.appendValueInput('URL')
        .appendField('URL');

    this.appendDummyInput('WITH_STATEMENT')
        .appendField('with results')
        .appendField(new Blockly.FieldCheckbox('FALSE', function (option) {
            var delayInput = (option == true);
            this.sourceBlock_.updateShape_(delayInput);
        }), 'WITH_STATEMENT');

    this.appendDummyInput('LOG')
        .appendField('log level')
        .appendField(new Blockly.FieldDropdown([
            ['none',  ''],
            ['info',  'log'],
            ['debug', 'debug'],
            ['warning',  'warn'],
            ['error', 'error']
        ]), 'LOG');

    this.setInputsInline(false);
    this.setPreviousStatement(true, null);
    this.setNextStatement(true, null);

    this.setColour(230);
    this.setTooltip('Request URL');
    this.setHelpUrl('https://github.com/request/request');
},
mutationToDom: function() {
    var container = document.createElement('mutation');
    container.setAttribute('with_statement', this.getFieldValue('WITH_STATEMENT') === 'TRUE');
    return container;
},
domToMutation: function(xmlElement) {
    this.updateShape_(xmlElement.getAttribute('with_statement') == 'true');
},
updateShape_: function(withStatement) {
    // Add or remove a statement Input.
    var inputExists = this.getInput('STATEMENT');

    if (withStatement) {
        if (!inputExists) {
            this.appendStatementInput('STATEMENT');
        }
    } else if (inputExists) {
        this.removeInput('STATEMENT');
    }
}};

Blockly.JavaScript['request'] = function(block) {
var logLevel = block.getFieldValue('LOG');
var URL = Blockly.JavaScript.valueToCode(block, 'URL', Blockly.JavaScript.ORDER_ATOMIC);
var withStatement = block.getFieldValue('WITH_STATEMENT');

var logText;
if (logLevel) {
    logText = 'console.' + logLevel + '("request: " + ' + URL + ');\n'
} else {
    logText = '';
}

if (withStatement === 'TRUE') {
    var statement = Blockly.JavaScript.statementToCode(block, 'STATEMENT');
    if (statement) {

        var xmlhttp = "var xmlHttp = new XMLHttpRequest();";
        var xmlopen = "xmlHttp.open('POST', " + URL + ", true);";
        var xmlheaders = "xmlHttp.setRequestHeader('Content-type', 'application/json');\n" + 
                         "xmlHttp.setRequestHeader('Authorization', 'Bearer psokmCxKjfhk7qHLeYd1');";
        var xmlonload = "xmlHttp.onload = function() {\n" +
                        "  console.log('recieved:' + this.response);\n" +
                        "  var response = this.response;\n" +
                        "  var brightness = 'brightness: ' + JSON.parse(this.response).payload.devices[0].brightness;\n" +
                        "  " + statement + "\n" +
                        "}";

        var json = JSON.stringify({
            "requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf",
            "inputs": [{
                "intent": "action.devices.QUERY",
                "payload": {
                    "devices": [{
                        "id": "0",
                        "customData": {
                            "smartHomeProviderId": "FkldJVJCmDNSaoLkoq0txiz8Byf2Hr"
                        }
                    }]
                }
            }]
        });

        var xmlsend = "xmlHttp.send('" + json + "');";

        var code = xmlhttp + '\n' + xmlopen + '\n' + xmlheaders + '\n' + xmlonload + '\n' + xmlsend;
        return code;

    } else {

        var xmlhttp = "var xmlHttp = new XMLHttpRequest();";
        var xmlopen = "xmlHttp.open('POST', " + URL + ", true);";
        var xmlheaders = "xmlHttp.setRequestHeader('Content-type', 'application/json');\n" + 
                         "xmlHttp.setRequestHeader('Authorization', 'Bearer psokmCxKjfhk7qHLeYd1');";
        var xmlonload = "xmlHttp.onload = function() {\n" +
                        "  console.log('recieved:' + this.response);\n" +
                        "}";

        var json = JSON.stringify({
            "requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf",
            "inputs": [{
                "intent": "action.devices.QUERY",
                "payload": {
                    "devices": [{
                        "id": "0",
                        "customData": {
                            "smartHomeProviderId": "FkldJVJCmDNSaoLkoq0txiz8Byf2Hr"
                        }
                    }]
                }
            }]
        });

        var xmlsend = "xmlHttp.send('" + json + "');";

        var code = xmlhttp + '\n' + xmlopen + '\n' + xmlheaders + '\n' + xmlonload + '\n' + xmlsend;
        return code;
    }
} else {
        var xmlhttp = "var xmlHttp = new XMLHttpRequest();";
        var xmlopen = "xmlHttp.open('POST', " + URL + ", true);";
        var xmlheaders = "xmlHttp.setRequestHeader('Content-type', 'application/json');\n" + 
                         "xmlHttp.setRequestHeader('Authorization', 'Bearer psokmCxKjfhk7qHLeYd1');";
        var xmlonload = "xmlHttp.onload = function() {\n" +
                        "  console.log('recieved:' + this.response);\n" +
                        "}";

        var json = JSON.stringify({
            "requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf",
            "inputs": [{
                "intent": "action.devices.QUERY",
                "payload": {
                    "devices": [{
                        "id": "0",
                        "customData": {
                            "smartHomeProviderId": "FkldJVJCmDNSaoLkoq0txiz8Byf2Hr"
                        }
                    }]
                }
            }]
        });

        var xmlsend = "xmlHttp.send('" + json + "');";

        var code = xmlhttp + '\n' + xmlopen + '\n' + xmlheaders + '\n' + xmlonload + '\n' + xmlsend;
        return code;
}};

2 ответа

На самом деле мы широко используем Promises в нашей среде Blockly. Мое предложение было бы обещать это, а затем использовать функцию генератора. Например, мы используем библиотеку co, чтобы обернуть наш сгенерированный код, а затем используем операторы yield для асинхронных значений, чтобы заставить их вести себя синхронно. Например:

Для настройки блока, подобной этой -

Блок переменных, устанавливающий переменную с именем getUsername в значение из БД,

Сгенерированный код будет выглядеть примерно так (упрощенно) -

co(function* () {
    var getUsername;
    // getFieldValue makes an asynchronous call to our database
    getUsername = (yield getFieldValue("username", "my user record Id", "users"));
    Promise.all(inputPromises)
    let inputPromises = [];
    inputPromises.push('User name is');
    inputPromises.push(getUsername);

    yield new Promise(function(resolve, reject) {
        Promise.all(inputPromises).then(function(inputResults) {
            let [TITLE, MESSAGE] = inputResults;
            let activity = "toastMessage";
            let LEVEL = "success";
            try {
                var params = {message: MESSAGE, title: TITLE, level: LEVEL};
                interface.notification(params);
                return resolve();
            } catch(err) {
                return reject(err);
            }

        }).catch(reject);

    });
    return true;
}

Как вы, наверное, заметили, это не всегда так просто, как просто поставить "доходность" перед блоком. В зависимости от вашей настройки вам, возможно, придется проявить больше творчества, используя Promise.all, чтобы получить значения в вашем блоке и т. Д. (Мы фактически закончили редактирование группы блоков Blockly, чтобы добавить "yield" перед входами, которые имели Тип "обещание" установлен среди их типов вывода, чтобы заставить это работать, но в зависимости от ваших настроек это может быть излишним.)

Очевидно, вам необходимо убедиться, что это было либо передано, либо запущено в среде, которая поддерживает ES6.

Конечно, как только вы входите в асинхронную настройку, возвращаться некуда - сами функции co возвращают Promise, так что вам нужно будет соответствующим образом с этим справиться. Но в целом мы нашли, что это довольно надежное решение, и я рад помочь вам разобраться в этом более подробно.

Вы можете иметь блоки, которые выполняются асинхронно без обещаний, async функции или обратные вызовы с использованием интерпретатора JS ( docs, GitHub), удобно написанного тем же человеком, который создал Blockly. JS Interpreter - это реализация JavaScript в JavaScript. Это означает, что для соединения функций / команд основной виртуальной машины JS со встроенной реализацией интерпретатора требуется много кода.

У Blockly есть несколько демонстраций этого ( src). В частности, вы хотите исследовать async-execution.html и реализация блока ожидания. Вы можете найти живой блок ожидания здесь.

Удобно, что в разделе документа переводчика по внешнему API используется XMLHttpRequest в качестве примера реализации. Это должно стать хорошей отправной точкой для реализации вашего блока запросов.

Другие вопросы по тегам