Skip to main content

JavaScript Promises and Async/Await for SAP Developers

If you're an SAP developer transitioning to JavaScript — especially for UI5, CAP, or Node.js development — understanding asynchronous programming is crucial. JavaScript's async model is fundamentally different from ABAP's synchronous execution, and Promises and async/await are the keys to mastering it.

This guide explains async JavaScript concepts using familiar SAP analogies and real-world SAP integration scenarios.

The Synchronous vs Asynchronous Mindset

ABAP World (Synchronous)

" ABAP - Everything happens in sequence
DATA: lv_customer TYPE kunnr,
      lt_orders   TYPE TABLE OF vbak,
      lv_total    TYPE netwr.

" Step 1: Get customer (WAIT for database)
SELECT SINGLE kunnr 
  FROM kna1 
  INTO lv_customer 
  WHERE name1 = 'ACME Corp'.

" Step 2: Get orders (WAIT for database)
SELECT * 
  FROM vbak 
  INTO TABLE lt_orders 
  WHERE kunnr = lv_customer.

" Step 3: Calculate total (WAIT for calculation)
LOOP AT lt_orders INTO DATA(ls_order).
  lv_total = lv_total + ls_order-netwr.
ENDLOOP.

WRITE: / 'Total:', lv_total.

" Everything happens ONE AFTER ANOTHER - blocking

JavaScript World (Asynchronous)

// JavaScript - Multiple things can happen concurrently
async function getCustomerOrders() {
  // Step 1: Start customer fetch (DON'T WAIT - continue immediately)
  const customerPromise = fetch('/api/customer?name=ACME Corp');
  
  // Step 2: Start orders fetch (DON'T WAIT - continue immediately)
  const ordersPromise = fetch('/api/orders');
  
  // Step 3: Now WAIT for both to complete (parallel execution)
  const [customerResponse, ordersResponse] = await Promise.all([
    customerPromise,
    ordersPromise
  ]);
  
  const customer = await customerResponse.json();
  const orders = await ordersResponse.json();
  
  // Step 4: Calculate total
  const total = orders.reduce((sum, order) => sum + order.netwr, 0);
  
  console.log('Total:', total);
}

// Code doesn't block - UI remains responsive

Understanding Promises

SAP Analogy: Work Order System

Think of a Promise like submitting a work order in SAP:

  • Pending: Work order submitted, waiting for technician (initial state)
  • Fulfilled: Work completed successfully (resolved with result)
  • Rejected: Work failed or cancelled (rejected with error)

Promise Basics

// Creating a Promise - like submitting a work order
function fetchCustomerData(customerId) {
  return new Promise((resolve, reject) => {
    // Simulate database call
    setTimeout(() => {
      if (customerId) {
        // Success - resolve with data
        resolve({ id: customerId, name: 'ACME Corp', status: 'Active' });
      } else {
        // Failure - reject with error
        reject(new Error('Customer ID required'));
      }
    }, 1000); // 1 second delay
  });
}

// Using the Promise
fetchCustomerData('C001')
  .then(customer => {
    console.log('Customer:', customer.name);
    // Chain another async operation
    return fetchOrders(customer.id);
  })
  .then(orders => {
    console.log('Orders:', orders.length);
  })
  .catch(error => {
    console.error('Error:', error.message);
  })
  .finally(() => {
    console.log('Operation complete');
  });

Promise States

StateDescriptionSAP Analogy
PendingInitial state, neither fulfilled nor rejectedWork order submitted
FulfilledOperation completed successfullyWork order completed (TECO)
RejectedOperation failedWork order cancelled/failed
SettledEither fulfilled or rejected (final state)Work order closed

Async/Await: Syntactic Sugar

Making Async Code Look Synchronous

// WITHOUT async/await (Promise chains)
function getCustomerWithOrders_OLD(customerId) {
  return fetchCustomer(customerId)
    .then(customer => {
      return fetchOrders(customer.id)
        .then(orders => {
          return {
            customer: customer,
            orders: orders,
            total: orders.reduce((sum, o) => sum + o.amount, 0)
          };
        });
    })
    .catch(error => {
      console.error('Error:', error);
      throw error;
    });
}

// WITH async/await (looks like ABAP!)
async function getCustomerWithOrders_NEW(customerId) {
  try {
    const customer = await fetchCustomer(customerId);
    const orders = await fetchOrders(customer.id);
    
    return {
      customer: customer,
      orders: orders,
      total: orders.reduce((sum, o) => sum + o.amount, 0)
    };
  } catch (error) {
    console.error('Error:', error);
    throw error;
  }
}

// Both do the same thing, but async/await is more readable

Key Rules of Async/Await

  • async keyword makes a function return a Promise
  • await can only be used inside async functions
  • await pauses execution until Promise resolves
  • Use try/catch for error handling
  • Always return values from async functions

Real-World SAP Scenarios

Scenario 1: SAP UI5 OData Service Calls

// BAD - Callback hell (old UI5 style)
this.getView().getModel().read("/CustomerSet('C001')", {
  success: function(customer) {
    this.getView().getModel().read("/OrderSet", {
      filters: [new Filter("CustomerId", "EQ", customer.CustomerId)],
      success: function(orders) {
        this.getView().getModel().read("/ProductSet", {
          success: function(products) {
            // Do something with all data
            this._processData(customer, orders, products);
          }.bind(this),
          error: function(err) { console.error(err); }
        });
      }.bind(this),
      error: function(err) { console.error(err); }
    });
  }.bind(this),
  error: function(err) { console.error(err); }
});

// GOOD - Async/await (modern approach)
async loadCustomerData() {
  try {
    const oModel = this.getView().getModel();
    
    // Wrap OData call in Promise
    const customer = await new Promise((resolve, reject) => {
      oModel.read("/CustomerSet('C001')", {
        success: resolve,
        error: reject
      });
    });
    
    const orders = await new Promise((resolve, reject) => {
      oModel.read("/OrderSet", {
        filters: [new sap.ui.model.Filter("CustomerId", "EQ", customer.CustomerId)],
        success: resolve,
        error: reject
      });
    });
    
    const products = await new Promise((resolve, reject) => {
      oModel.read("/ProductSet", {
        success: resolve,
        error: reject
      });
    });
    
    this._processData(customer, orders, products);
    
  } catch (error) {
    sap.m.MessageBox.error("Failed to load data: " + error.message);
  }
}

Scenario 2: SAP CAP Service Implementation

// SAP Cloud Application Programming (CAP) - Node.js
module.exports = cds.service.impl(async function() {
  
  const { Customers, Orders, Products } = this.entities;
  
  // Handle custom action with async/await
  this.on('calculateCustomerValue', async (req) => {
    const { customerId } = req.data;
    
    try {
      // Multiple database queries in parallel
      const [customer, orders, payments] = await Promise.all([
        SELECT.one.from(Customers).where({ ID: customerId }),
        SELECT.from(Orders).where({ customer_ID: customerId }),
        SELECT.from('Payments').where({ customer_ID: customerId })
      ]);
      
      if (!customer) {
        return req.error(404, 'Customer not found');
      }
      
      // Calculate total order value
      const orderValue = orders.reduce((sum, order) => 
        sum + parseFloat(order.total), 0);
      
      // Calculate total payments
      const paymentTotal = payments.reduce((sum, payment) => 
        sum + parseFloat(payment.amount), 0);
      
      // Calculate outstanding balance
      const outstanding = orderValue - paymentTotal;
      
      return {
        customer: customer.name,
        totalOrders: orders.length,
        orderValue: orderValue,
        paymentTotal: paymentTotal,
        outstanding: outstanding,
        creditStatus: outstanding > 10000 ? 'BLOCKED' : 'OK'
      };
      
    } catch (error) {
      req.error(500, `Calculation failed: ${error.message}`);
    }
  });
  
});

Scenario 3: External API Integration

// Fetching data from multiple SAP and non-SAP systems
async function getConsolidatedReport(companyCode) {
  try {
    // Parallel execution of multiple API calls
    const [
      erpData,        // SAP ERP via REST API
      s4hanaData,     // S/4HANA OData
      crmData,        // Third-party CRM
      exchangeRate    // Currency exchange API
    ] = await Promise.all([
      fetch(`https://erp.company.com/api/financial?companyCode=${companyCode}`)
        .then(res => res.json()),
      
      fetch(`https://s4hana.company.com/sap/opu/odata/SAP/FINANCIAL_SRV/Results?$filter=CompanyCode eq '${companyCode}'`)
        .then(res => res.json()),
      
      fetch(`https://crm.external.com/api/customers?company=${companyCode}`)
        .then(res => res.json()),
      
      fetch('https://api.exchangerate.host/latest?base=USD')
        .then(res => res.json())
    ]);
    
    // Process consolidated data
    return {
      companyCode,
      erpRevenue: erpData.revenue,
      s4hanaOrders: s4hanaData.d.results.length,
      crmCustomers: crmData.customers.length,
      exchangeRate: exchangeRate.rates.EUR,
      timestamp: new Date().toISOString()
    };
    
  } catch (error) {
    console.error('Consolidation failed:', error);
    throw new Error(`Failed to consolidate report: ${error.message}`);
  }
}

// Usage
getConsolidatedReport('1000')
  .then(report => console.log('Report:', report))
  .catch(error => console.error('Error:', error));

Advanced Patterns

Sequential vs Parallel Execution

// SEQUENTIAL - One after another (slow)
async function processOrders_Sequential(orderIds) {
  const results = [];
  
  for (const orderId of orderIds) {
    const order = await fetchOrder(orderId);  // WAIT for each
    const status = await checkStatus(orderId); // WAIT for each
    results.push({ order, status });
  }
  
  return results;
  // If each takes 1s, total time = 2s * orderIds.length
}

// PARALLEL - All at once (fast)
async function processOrders_Parallel(orderIds) {
  const promises = orderIds.map(async (orderId) => {
    const [order, status] = await Promise.all([
      fetchOrder(orderId),    // Both happen simultaneously
      checkStatus(orderId)
    ]);
    return { order, status };
  });
  
  return await Promise.all(promises);
  // Total time ≈ 1s regardless of orderIds.length
}

Error Handling Strategies

// Strategy 1: Fail Fast (like ABAP exceptions)
async function processOrder_FailFast(orderId) {
  try {
    const order = await fetchOrder(orderId);
    const validation = await validateOrder(order);
    const result = await saveOrder(order);
    return result;
  } catch (error) {
    // Any error stops entire process
    throw new Error(`Order processing failed: ${error.message}`);
  }
}

// Strategy 2: Continue on Error (like ABAP CONTINUE)
async function processBatch_ContinueOnError(orderIds) {
  const results = await Promise.allSettled(
    orderIds.map(id => processOrder(id))
  );
  
  const successful = results
    .filter(r => r.status === 'fulfilled')
    .map(r => r.value);
  
  const failed = results
    .filter(r => r.status === 'rejected')
    .map(r => ({ id: r.reason.orderId, error: r.reason.message }));
  
  return { successful, failed };
}

// Strategy 3: Retry Logic (like ABAP RETRY)
async function processOrder_WithRetry(orderId, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await processOrder(orderId);
    } catch (error) {
      if (attempt === maxRetries) {
        throw error; // Give up after max retries
      }
      console.log(`Retry ${attempt}/${maxRetries} for order ${orderId}`);
      await sleep(1000 * attempt); // Exponential backoff
    }
  }
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

Timeout Pattern

// Like ABAP's WAIT UP TO with timeout
function withTimeout(promise, timeoutMs) {
  return Promise.race([
    promise,
    new Promise((_, reject) => 
      setTimeout(() => reject(new Error('Timeout exceeded')), timeoutMs)
    )
  ]);
}

// Usage
async function fetchWithTimeout() {
  try {
    const data = await withTimeout(
      fetch('https://slow-api.com/data'),
      5000  // 5 second timeout
    );
    return data;
  } catch (error) {
    if (error.message === 'Timeout exceeded') {
      console.error('API call timed out');
    }
    throw error;
  }
}

Common Pitfalls for SAP Developers

❌ Pitfall 1: Forgetting await

// WRONG - Returns Promise, not data
async function getCustomer(id) {
  return fetchCustomer(id); // Missing await!
}

const customer = getCustomer('C001');
console.log(customer.name); // ERROR: Cannot read property 'name' of Promise

// CORRECT
async function getCustomer(id) {
  return await fetchCustomer(id);
}

const customer = await getCustomer('C001');
console.log(customer.name); // Works!

❌ Pitfall 2: Using await in Loops Unnecessarily

// SLOW - Sequential execution
async function fetchAllCustomers_SLOW(ids) {
  const customers = [];
  for (const id of ids) {
    customers.push(await fetchCustomer(id)); // One at a time
  }
  return customers;
}

// FAST - Parallel execution
async function fetchAllCustomers_FAST(ids) {
  return await Promise.all(
    ids.map(id => fetchCustomer(id)) // All at once
  );
}

❌ Pitfall 3: Not Handling Rejections

// BAD - Unhandled promise rejection
async function processData() {
  const data = await fetchData(); // If this fails, error is unhandled
  return data;
}

// GOOD - Always use try/catch
async function processData() {
  try {
    const data = await fetchData();
    return data;
  } catch (error) {
    console.error('Failed to fetch data:', error);
    return null; // Or throw, or return default value
  }
}

Best Practices Summary

PracticeDescriptionExample
Always await PromisesDon't forget await or .then()const data = await fetch(url);
Use try/catchHandle errors explicitlytry { ... } catch (e) { ... }
Parallel when possibleUse Promise.all for independent tasksPromise.all([p1, p2])
Return from async functionsAlways return a value or Promisereturn await getData();
Use async/await over .then()More readable, easier to debugawait func() vs func().then()
Add timeoutsPrevent hanging operationswithTimeout(promise, 5000)

Debugging Async Code

VS Code Debugger

// Set breakpoints in async functions
async function debugExample() {
  console.log('Start');
  
  const data1 = await fetchData1(); // Breakpoint here
  console.log('Data1:', data1);
  
  const data2 = await fetchData2(); // Breakpoint here
  console.log('Data2:', data2);
  
  return { data1, data2 };
}

// VS Code will properly pause at each await

Logging Best Practices

async function loggedOperation(orderId) {
  console.log(`[START] Processing order ${orderId}`);
  
  try {
    const order = await fetchOrder(orderId);
    console.log(`[FETCH] Order retrieved:, order.number`);
    
    const result = await processOrder(order);
    console.log(`[SUCCESS] Order processed: ${result.status}`);
    
    return result;
  } catch (error) {
    console.error(`[ERROR] Order ${orderId} failed:`, error.message);
    throw error;
  } finally {
    console.log(`[END] Processing order ${orderId}`);
  }
}

Comparison Table: ABAP vs JavaScript Async

ConceptABAPJavaScript
ExecutionAlways synchronousAsynchronous by default
Database callsSELECT (blocking)await fetch() (non-blocking)
Error handlingTRY...CATCH...ENDTRYtry...catch (same concept!)
Parallel executionRFC parallel processingPromise.all()
TimeoutWAIT UP TOPromise.race() with setTimeout
Return valueRETURN lv_valuereturn await getValue()

Conclusion

For SAP developers, the shift to asynchronous JavaScript can feel uncomfortable at first. However, understanding Promises and async/await unlocks the full power of modern JavaScript:

  • Responsive UIs – UI5 apps that don't freeze
  • Better performance – Parallel operations instead of sequential
  • Modern APIs – All new JavaScript APIs are Promise-based
  • Clean code – async/await makes async code look synchronous

Think of async/await as JavaScript's way of handling long-running operations without blocking — just like ABAP RFC calls, but built into the language itself.

About the Author: Yogesh Pandey is a passionate developer and consultant specializing in SAP technologies and full-stack development.