by mafredri

mafredri / zsh-async

Because your terminal should be able to perform tasks asynchronously without external tools!

468 Stars 26 Forks Last release: 27 days ago (v1.8.5) MIT License 211 Commits 23 Releases

Available items

No Items, yet!

The developer of this repository has not created any items for sale yet. Need a bug fixed? Help with integration? A different license? Create a request here:


Because your terminal should be able to perform tasks asynchronously without external tools!

Intro (TL;DR)


you can run multiple asynchronous jobs, enforce unique jobs (multiple instances of the same job will not run), flush all currently running jobs and create multiple workers (each with their own jobs). For each worker you can register a callback-function through which you will be notified about the job results (job name, return code, output and execution time).


is a small library for running asynchronous tasks in zsh without requiring any external tools. It utilizes
to launch a pseudo-terminal in which all commands get executed without blocking any other processes. Checking for completed tasks can be done manually, by polling, or better yet, automatically whenever a process has finished executing by notifying through a

This library bridges the gap between spawning child processes and disowning them. Child processes launched by normal means clutter the terminal with output about their state, and disowned processes become separate entities, no longer under control of the parent. Now you can have both!


The async worker is a separate environment (think web worker). You send it a job (command + parameters) to execute and it returns the result of that execution through a callback function. If you find that you need to stop/start a worker to update global state (variables) you should consider refactoring so that state is passed during the

call (e.g.
async_job my_worker my_function $state1 $state2



You can either source the

script directly or insert under your
as async and autoload it through
autoload -Uz async && async


zplug "mafredri/zsh-async", from:"github", use:"async.zsh"



library has a bunch of functions that need to be used to perform async actions:


Initializes the async library (not required if using async from

with autoload.)

async_start_worker  [-u] [-n] [-p ]

Start a new async worker with optional parameters, a worker can be told to only run unique tasks and to notify a process when tasks are complete.

  • -u
    unique. Only unique job names can run, e.g. the command
    git status
    will have
    as the unique job name identifier
  • -n
    notify through
    signal. Needs to be caught with a
    trap '' WINCH
    in the process defined by

NOTE: When

is used in an interactive shell with ZLE enabled this option is not needed. Signaling through
has been replaced by a ZLE watcher that is triggered on output from the
instance (still requires a callback function through
though). Technically zsh versions prior to
do not return the file descriptor for zpty instances, however,
attempts to deduce it anyway.
  • -p
    pid to notify (defaults to current pid)

async_stop_worker  []

Simply stops a worker and all active jobs will be terminated immediately.

async_job   []

Start a new asynchronous job on specified worker, assumes the worker is running.

async_worker_eval   []

Evaluate a command (like async_job) inside the async worker, then worker environment can be manipulated. For example, issuing a cd command will change the PWD of the worker which will then be inherited by all future async jobs.

Output will be returned via callback, job name will be [async/eval].


Get results from finished jobs and pass it to the to callback function. This is the only way to reliably return the job name, return code, output and execution time and with minimal effort.

If the async process buffer becomes corrupt, the callback will be invoked with the first argument being

(job name), non-zero return code and fifth argument describing the error (stderr).


is called with the following parameters:
  • $1
    job name, e.g. the function passed to async_job
  • $2
    return code
    • Returns
      if return code is missing, this should never happen, if it does, you have likely run into a bug. Please open a new issue with a detailed description of what you were doing.
  • $3
    resulting (stdout) output from job execution
  • $4
    execution time, floating point e.g. 0.0076138973 seconds
  • $5
    resulting (stderr) error output from job execution
  • $6
    has next result in buffer (0 = buffer empty, 1 = yes)
    • This means another async job has completed and is pending in the buffer, it's very likely that your callback function will be called a second time (or more) in this execution. It's generally a good idea to e.g. delay prompt updates (
      zle reset-prompt
      ) until the buffer is empty to prevent strange states in ZLE.

Possible error return codes for the job name

  • 1
    Corrupt worker output.
  • 2
    ZLE watcher detected an error on the worker fd.
  • 3
    Response from async_job when worker is missing.
  • 130
    Async worker crashed, this should not happen but it can mean the file descriptor has become corrupt. This must be followed by a
    async_stop_worker [name]
    and then the worker and tasks should be restarted. It is unknown why this happens.


Register a callback for completed jobs. As soon as a job is finished,

will be called with the specified callback function. This requires that a worker is initialized with the -n (notify) option.


Unregister the callback for a specific worker.


Flush all current jobs running on a worker. This will terminate any and all running processes under the worker by sending a

to the entire process group, use with caution.

Example code

#!/usr/bin/env zsh
source ./async.zsh

Initialize a new worker (with notify option)

async_start_worker my_worker -n

Create a callback function to process results

COMPLETED=0 completed_callback() { COMPLETED=$(( COMPLETED + 1 )) print [email protected] }

Register callback function for the workers completed jobs

async_register_callback my_worker completed_callback

Give the worker some tasks to perform

async_job my_worker print hello async_job my_worker sleep 0.3

Wait for the two tasks to be completed

while (( COMPLETED < 2 )); do print "Waiting..." sleep 0.1 done

print "Completed $COMPLETED tasks!"



print 0 hello 0.001583099365234375



sleep 0 0.30631208419799805

Completed 2 tasks!


Tests are located in

and can be run by executing the test runner:


$ ./test.zsh
ok  ./async_test.zsh    2.334s

The test suite can also run specific tasks that match a pattern, for example:

$ ./test.zsh -v -run zle
=== RUN   test_zle_watcher
--- PASS: test_zle_watcher (0.07s)
ok  ./async_test.zsh    0.070s


  • A NULL-character (
    ) is used by
    to signify the end of the command, it is recommended not to pass them as arguments, although they should work when passing multiple arguments to
    (because of quoting).
  • Tell me? :)


If you do not wish to use the

feature, you can couple
or the zsh
function for scheduling the worker results to be processed.

Why did I make this?

I found a great theme for zsh, Pure by Sindre Sorhus. After using it for a while I noticed some graphical glitches due to the terminal being updated by a disowned process. Thus, I became inspired to get my hands dirty and find a solution. I tried many things, coprocesses (seemed too limited by themselves), different combinations of trapping kill-signals, etc. I also had problems with the zsh process ending up in a deadlock due to some zsh bug. After working out the kinks, I ended up with this and thought, hey, why not make it a library.

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.