JavaScript
Google API
gapi
async/await
client library

How to use Google API Client Library for JavaScript gapi with async/await?

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

The JavaScript Google API client was designed before async and await became normal, so the awkward part is usually initialization, not the request itself. Once script loading and token acquisition are wrapped in promises, the rest of the code can be written as ordinary async functions.

The practical pattern is to separate three steps: load gapi, initialize the client, and request an access token. After that, calling a Google API with await becomes straightforward.

Load gapi and Initialize the Client

In a browser app you typically include the Google API script plus Google Identity Services:

html
<script async defer src="https://apis.google.com/js/api.js"></script>
<script async defer src="https://accounts.google.com/gsi/client"></script>

The gapi.load function is callback-based, so wrap it in a promise:

javascript
1function loadClient() {
2  return new Promise((resolve, reject) => {
3    gapi.load("client", async () => {
4      try {
5        await gapi.client.init({
6          apiKey: API_KEY,
7          discoveryDocs: [DISCOVERY_DOC],
8        });
9        resolve();
10      } catch (error) {
11        reject(error);
12      }
13    });
14  });
15}

Now you can write:

javascript
await loadClient();

That already removes the biggest source of nesting.

Wrap Token Requests Too

Authentication is similar. Google Identity Services uses a callback when the token request finishes, so wrap that as well:

javascript
1const tokenClient = google.accounts.oauth2.initTokenClient({
2  client_id: CLIENT_ID,
3  scope: SCOPES,
4});
5
6function requestAccessToken() {
7  return new Promise((resolve, reject) => {
8    tokenClient.callback = (response) => {
9      if (response.error) {
10        reject(response);
11        return;
12      }
13      resolve(response);
14    };
15
16    const prompt = gapi.client.getToken() ? "" : "consent";
17    tokenClient.requestAccessToken({ prompt });
18  });
19}

This lets authorization fit naturally into your async control flow.

Call an API with await

Once the client is ready and a token exists, the request itself reads cleanly. Here is a Google Sheets example:

javascript
1async function loadSheetRows() {
2  const response = await gapi.client.sheets.spreadsheets.values.get({
3    spreadsheetId: SPREADSHEET_ID,
4    range: "Sheet1!A2:B10",
5  });
6
7  return response.result.values ?? [];
8}

And now the overall sequence is easy to follow:

javascript
1async function main() {
2  await loadClient();
3  await requestAccessToken();
4
5  const rows = await loadSheetRows();
6  console.log(rows);
7}
8
9main().catch(console.error);

That is the main win of async and await: setup and request flow become readable from top to bottom.

Keep Setup and Business Logic Separate

A common mistake is putting all of the following into one click handler:

  • client loading
  • token acquisition
  • API call
  • page rendering

That works once, but it becomes painful to retry, debug, or test. A cleaner split is:

  1. loadClient
  2. requestAccessToken
  3. API-specific functions such as loadSheetRows

This also makes error messages more useful because you know whether the failure came from script initialization, authentication, or the API call itself.

Use try and catch Around the Whole Flow

Once callback boundaries are wrapped, normal error handling works well:

javascript
1async function handleAuthorizeClick() {
2  try {
3    await loadClient();
4    await requestAccessToken();
5    const rows = await loadSheetRows();
6    renderRows(rows);
7  } catch (error) {
8    console.error("Google API flow failed:", error);
9  }
10}

This is much easier to maintain than several nested error callbacks spread across different initialization steps.

Common Pitfalls

  • Trying to await gapi.load(...) directly even though it is callback-based.
  • Mixing old Google sign-in samples with the newer Google Identity Services token flow.
  • Calling gapi.client methods before gapi.client.init has finished.
  • Keeping initialization, authentication, and API logic tangled together in one function.
  • Treating every failure as an API problem when the real issue is often initialization or token setup.

Summary

  • 'gapi works well with async and await once you wrap its callback-based setup in promises.'
  • Initialize the client first, then acquire a token, then make API calls.
  • 'gapi.client request methods can usually be awaited naturally after setup.'
  • Keep loading, authentication, and business logic as separate functions.
  • Use normal try and catch once the callback boundaries have been converted.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design