SWI-Prolog 9.2.6 Reference Manual: Section 13.3

## 13.3 Accessing JavaScript from Prolog

This section describes how we can interact with JavaScript from Prolog. The interface is captured in a single predicate :=/2/.

Left := Right
  Depending on Left, this predicate implements two different actions. If Left is a Prolog variable, it evaluates the expression Right in JavaScript and unifies the result to Left. If Left is a term Obj[Key], where Key is an atom, it accesses a JavaScript setter. The general form of an expression is Expression[Callable] or simply Callable. If Callable is compound it expresses a function (or method) call. Otherwise we call JavaScript eval(), except for these special values:
  window
    The main browser window itself (`undefined` when not in a browser).
  prolog
    The `Prolog` instance.

  Prolog values are translated according to the rules in section 13.2.2.2 and the result is translated back to Prolog according to the rules in section 13.2.2.1. Because callables are translated to function calls, object properties or global variables we need an escape to pass them as data. This is achieved using the prefix operator ``#``. Note that lists are passed as JavaScript arrays rather than calls to the list functor. For convenience Prolog strings are by default translated to JavaScript `String` objects rather than `Prolog.String` instances. Below are some examples:

      ?- Res := myfunc([1,2,3]).
      ?- Max := 'Math'.max(10, 20).
      ?- Out := document.getElementById('output').
      ?- Par := document.createElement(p),
         Par.textContent := #Text.
      ?- Par.textContent := "aap" + " " + "noot".

  Some JavaScript expressions are not implemented as functions. The following “functions” are handled directly by the implementation.

  instanceof
    Returns the name of the class to which the object belongs. Same as `Obj.constructor.name`.
  instanceof(ClassName)
    Returns a `Boolean` indicating whether the object is an instance of ClassName. Note that the class name must be an atom and as JavaScript class names normally start with a capital, the names dypically need to be quoted using single quotes. For example:

        ?- W := window, T := W.instanceof('Window').
        W = <js_Window>(1),
        T = true.

  `-`(Any)
    Numerical negation
  `!`(Any)
    Logical negation.
  `+`(Any, Any)
  `-`(Any, Any)
  `*`(Any, Any)
  `/`(Any, Any)
  `&`(Any, Any)
  `|`(Any, Any)
  `&&`(Any, Any)
  `||`(Any, Any)
    Binary operators. Note that some are not defined as Prolog operators and thus one must write e.g. `A := &&(true,false)`. `||` is not a Prolog atom, so logical disjunction gets `A := '||'(false,false)`.
[semidet]is_object(@Term)
  True if Term is a reference to a JavaScript object.
[semidet]is_object(@Term, ?Class)
  True when Term is an instance of Class. If Class is unbound it is unified with the name of the constructor, otherwise a JavaScript `Term instanceof Class` is executed.
js_script(+String, +Options)
  Evaluate String as JavaScript. This is designed to cooperate with string quasi quotations, so we can write e.g.,

      :- use_module(library(strings)).
      :- js_script({|string||
      function myfunc(a)
      ...
      |}).

  The implementation uses =:/2, calling the JavaScript function eval().

fetch(+URL, +Type, -Data)
  Wrapper around JavaScript fetch(), conversion of the `Response` object and waiting for the `Promise`. Type is an atom or string that is used as method on the `Response` object. Examples are `text`, `json`, `html` or `blob`. The `blob` type returns the Data as a string of bytes, i.e., character codes in the range 0 ... 255.

#### 13.3.1 Asynchronous access to JavaScript from Prolog

While section 13.3 describes synchronous calls from Prolog to JavaScript, we also need asynchronous calling to implement sleep/1, wait for user input, downloading documents from the web, etc. Asynchronous calling is achieved by yielding from the Prolog virtual machine. This can only be done when Prolog is told to expect that the VM may yield. This is implemented by `Prolog.forEach()` as described in section 13.2.

[det]await(+Promise, -Result)
  Yield the Prolog VM, returning control back to JavaScript. When this is called from Prolog invoked using `Prolog.forEach()`, execution of await/2 completes when the Promise resolves and Result is unified with the value passed to the `Promise.then()` method. As an exception to the normal conversion rules, if the result is a single `String`, it is returned as a Prolog string rather than an atom. When the Promise is rejected await/2 throws an exception. Note that await/2 allows, for example, downloading a URL from Prolog:

      ?- FP := fetch("test.pl"), await(FP, Response),
         TP := Response.text(), await(TP, T).
      FP = <js_Promise>(4),
      Response = <js_Response>(5),
      TP = <js_Promise>(6),
      T = "% :- debug(js) ...".

  Calls to await/2 may be asynchronously aborted by calling `Prolog.abort()` if Promise implements `.abort()`. See section 13.3.2 for implementing such a promise.

[semidet]is_async
  True when we can call await/2 in the current state. This implies Prolog has been called from JavaScript code that is prepared to deal with Prolog yielding and Prolog is not inside a callback from C (WASM).

#### 13.3.2 JavaScript Promise that can be aborted

A `Promise` resolves or is rejected. As Prolog waits for a specific promise on a call to await/2 we may want to abort long running operations. This may be achieved using the class `Prolog.Promise` which extends `Promise`. To make the promise abortable the executor function must have an `abort` property. Below is the code for `Prolog.promise_sleep()` that implements this schema. First we create the executor and use properties on the function itself to represent the necessary state information (here, the running timer). Next, we add an `abort` property the clears the timer and runs the `reject` callback of the `Promise`. Finally we return an instance of `Prolog.Promise` which implements `.abort()`.

    promise_sleep(time)
    { const f = function(resolve, reject)
      { f.reject = reject;
        f.timer = setTimeout(() =>
          { f.timer = undefined;
            resolve(true);
          }, time*1000);
      };

      f.abort = function()
      { if ( f.timer )
        { clearTimeout(f.timer);
          f.timer = undefined;
          f.reject("abort");
        }
      }

      return new Prolog.Promise(f);
    }