aichaos / rivescript-js

A RiveScript interpreter for JavaScript. RiveScript is a scripting language for chatterbots.
https://www.rivescript.com/
MIT License
377 stars 144 forks source link

Use async functions for all object macros #275

Closed kirsle closed 6 years ago

kirsle commented 6 years ago

Before v2.0.0 stable, object macros parsed by RiveScript should be loaded in as async functions so that they may make use of the await keyword themselves.

Just add the word async in the function declaration here: https://github.com/aichaos/rivescript-js/blob/817c9d0eb106f0ee3e7974bf9e38f413d8436f7d/src/lang/javascript.js#L40

Note: because this could be backwards-incompatible (imagine a Node 6 environment trying to parse an async function and failing), RiveScript should test for async/await support in the environment first.

e.g. wrap a try/catch around eval("(async function() {})"), and if it fails, parse the object macro as a standard function as we've been doing previously.

In case an object macro uses the await keyword and your environment doesn't support async/await: the macro will fail to parse and log the syntax error, as would be expected. Macros that want to be backwards compatible can deal with Promises instead of using await, and normal string-returning macros will work all the same.

Example use case:

// This bot will detect if the user ever sends an identical message
// twice, even if they send other things in between.
> begin
    + request
    * <call>is-repeating</call> == true => {random}
    ^ You already said that.|
    ^ Not again.|
    ^ No more saying "<get origMessage>"{/random}
    - {ok}
< begin

// Because the object macro isn't async, Promises are a pain to deal with.
> object is-repeating javascript
    var user = rs.currentUser();
    return new Promise(function(resolve, reject) {
        // Get the user's original message.
        rs.getUservar(user, "origMessage").then(function(origMessage) {
            // Get the map where we store repeated triggers for the user.
            rs.getUservar(user, "repeat-map").then(function(map) {
                // Check if the map was initialized; create it if not.
                if (typeof(map) !== "object") {
                    map = new Object();
                }

                // Has the user already sent this message?
                if (map[origMessage] !== undefined) {
                    return resolve("true");
                }

                // Store this in the map.
                map[origMessage] = true;
                rs.setUservar(user, "repeat-map", map).then(function() {
                    resolve("false"); // they were not repeating themselves just now
                });
            });
        });
    });
< object

Whereas if the object macro was async, the code could resemble its v1.19.0 working counterpart:

> object is-repeating javascript
    var user = rs.currentUser();
    var origMessage = await rs.getUservar(user, "origMessage");
    var map = await rs.getUservar(user, "repeat-map");

    // Check if the map was initialized; create it if not.
    if (typeof(map) !== "object") {
         map = new Object();
    }

    // Has the user already sent this message?
    if (map[origMessage] !== undefined) {
         return resolve("true");
    }

    // Store this in the map.
    map[origMessage] = true;
    await rs.setUservar(user, "repeat-map", map)
    resolve("false");
< object

Effects