For example, the following code runs in about 1 second because both sleep/1 calls happen in parallel. When foo/0 unifies L, it blocks until silly/1 has finished.
silly(L) :-
    sleep(1),
    L = [a,b].
foo :-
    spawn(silly(L)),
    sleep(1),
    L=[A,B],  % blocks, if necessary
    writeln(A-B).
If Goal produces multiple solutions, they're iterated when
backtracking over the unification (L=[A,B] above). If Goal fails
or throws an exception, the calling thread sees it at the unification
point.
lazy thread policy.
lazy/1 can be helpful when complicated or expensive goals are only needed in some code paths but duplicating those goals is too verbose. It can be an alternative to creating a new, named predicate. For example,
foo(Xs) :-
    lazy(i_am_slow(a,B,[c(C),d(d),e(etc)])), % complicated
    ( day_of_week(tuesday) ->
        append(B,C,Xs)
    ; phase_of_moon(full) ->
        append(C,B,Xs)
    ; true ->
        % i_am_slow/3 not executed in this code path
        Xs = [hi]
    ).
Options are as follows:
ephemeral (default), create a new thread in which to call
goal. If lazy, only execute Goal when await/1 is called; no
background threads are used.await/1 strives to have the same determinism as the original Goal passed to async/3. If that goal fails, await/1 fails. If that goal throws an exception, so does await/1. If that goal produces many solutions, so does await/1 on backtracking.