Skip to main content

Asynchronous tasks


This article covers asynchronous tasks in JavaScript and provides code examples. It may be helpful to understand some JavaScript fundamentals before you continue. Check out the required knowledge here →

When an API request is made, the API’s response time varies depending on whether the task is synchronous or asynchronous. The Plugin API and Widget API include both synchronous and asynchronous functions.

In this article, we’ll explain the differences between the two. We'll also explore how asynchronous functions can make your API communication more effective.

Synchronous requests

With synchronous requests, the client must wait for a response before other requests are made. Synchronous requests are used when date—the response from the API—is expected to return immediately.

You have likely encountered these kinds of tasks in your own life. Like waiting for a photo to upload before you can continue using an application. During this time you could see a progress bar, spinning circle, or loading screen.

You can think of synchronous requests like getting in line at the grocery store or drive through. Each request is handled one at a time. Unlike at the grocery store, synchronous tasks can’t jump the queue. Those who were in line before you must complete their transactions before you can move forward and begin your own check out process.

Typically, code written in JavaScript runs one line at a time, in the order it was written. Consider the following code:

console.log("I am");

As JavaScript is synchronous by default, the console output looks like:

  1. “I am” is the first output in the JavaScript console.
  2. “synchronous” is the next output.
  3. “JavaScript” is the final output.

While synchronous programming works in this example, synchronous tasks aren’t always the best approach. Since each task must wait for the previous request to receive a response before proceeding, it can slow down larger, more complex applications where response times are unpredictable.

Asynchronous requests

Unlike synchronous requests, asynchronous requests don’t need to wait on a response before proceeding. Instead, a callback is provided to notify the client when a response is ready. Asynchronous requests are useful when an API’s response time varies based on the availability of a resource.

You can think of this as ordering something from a coffee shop. You get in line, your order is taken, then you give your name so the barista can call you when your order is ready. In an API, your name is the request token or unique identifier.

In a coffee shop, orders won’t always come out in the order they were placed. An americano will take less time than a frothy coffee with latte art! The same can be said of asynchronous requests.

An animation showing two rectangles. One labeled “Plugin” and the other labeled “API”. In an animation labeled “Async request” requests go from the Plugin to the API and responses go from the API to the Plugin in no particular order. In an animation labeled “Sync request” requests go from the Plugin to the API and responses return in the same order that requests were received.

This way, other requests can continue to process in the background and the application can keep its functionality while waiting for responses. An asynchronous approach can be an efficient solution when the API’s response time is unpredictable due to variables in data size and complexity.

Learn more about asynchronous JavaScript in the MDN Web Docs →


As the client doesn’t need to wait for a response from the API, we need another way to communicate the status of that task. This is where Promises come into play.

Asynchronous programming in modern JavaScript is built on the concept of Promises. A Promise is an object returned by an asynchronous request and can be in one of three states:

  • Pending: The promise has been created but the request has not been fulfilled or rejected.
  • Fulfilled: The asynchronous request was successful. When a promise is fulfilled, the .then() handler is called.
  • Rejected: The asynchronous request failed. When a promise is rejected, the .catch() handler is called.

An animation showing two rectangles. One labelled “Plugin” and the other labelled “API”. An animation labelled “Status: pending” shows a request in between the Plugin and API. An animation labelled “Status: rejected” shows the request being returned to the Plugin. An animation labelled “Status: fulfilled” shows the API sending a response to the Plugin.

Consider the following code:

const fetchPromise = fetch("");

fetchPromise.then((response) => {
const textPromise = response.text();
textPromise.then((data) => {

In this example, we are using the fetch()API to request some data from the Figma API. When we fetch() from the Figma API, a Promise is returned. An asynchronous function is helpful here because we don’t know how long it will take for the API to respond with the requested data.

Once the Promise state is fulfilled, the .then() handler is called. We are saying that if the response was successful, turn the response into JSON then output the data. The returned data should look something like this:

"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
"json": null,
"method": "GET",
"origin": "...",
"url": ""

If the Promise is rejected, an error is returned. We can view this error by adding the .catch() handler to our code:

const fetchPromise = fetch("");

.then((response) => {
const textPromise = response.text();
textPromise.then((data) => {
.catch((error) => {

Learn more about how to use Promises in the MDN Web Docs →

Async and Await

The async and await keywords allow us to use asynchronous functions that look like synchronous code. Using async and await can improve the performance of your application since it continues to run while waiting on a response. These keywords allow you to write asynchronous code in a neat, organized and readable way.

Adding async at the start of function definition turns it into an asynchronous function. Inside an asynchronous function, the await keyword can be used before a function call that returns a Promise. If we rewrite our earlier example using async and await, it would look like this:

async function fetchData(){
try {
const response = await fetch("");
const text = await response.text();
console.log(`Response text is: ${text}`);
} catch (error) {



With async and await, we can use try...catch blocks for error handling as if we were working with synchronous code.

Learn more about async and await in the MDN Web Docs →

Asynchronous tasks in the Figma Plugin API

While most functions in the Plugin API and Widget API are synchronous, here are some examples of asynchronous tasks in Figma:


This is the most common async function in the Plugin API. You'll need to use this function if you want your plugin to modify or re-render text nodes.

Font files aren’t always available in the browser’s memory. Especially if the file lives on a user’s local machine. If someone runs your plugin in a file that doesn’t have those fonts loaded, they won’t be able to edit any text nodes. The loadFontAsync function makes sure all fonts in the file are loaded and ready to use.

figma.ui.onmessage = async (pluginMessage) => {

await figma.loadFontAsync({ family: "Rubik", style: "Regular" });



This function exports a selected node as an encoded image. In the example below, a polygon is created then exported as a PNG at 2x resolution. We use await to ensure that the polygon object is properly created before we attempt to export it.

When settings for format and constraint aren’t specified, exportAsync defaults to exporting the node as a PNG at 1x resolution.

async () => {
const polygon = figma.createPolygon()
polygon.pointCount = 6
polygon.fills = [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }]

// Export a 2x resolution PNG of the node
const bytes = await polygon.exportAsync({
format: 'PNG',
constraint: { type: 'SCALE', value: 2 },

Here are other useful asynchronous functions in the Plugin API: