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 - blockingJavaScript 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 responsiveUnderstanding 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
| State | Description | SAP Analogy |
|---|---|---|
| Pending | Initial state, neither fulfilled nor rejected | Work order submitted |
| Fulfilled | Operation completed successfully | Work order completed (TECO) |
| Rejected | Operation failed | Work order cancelled/failed |
| Settled | Either 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 readableKey Rules of Async/Await
asynckeyword makes a function return a Promiseawaitcan only be used insideasyncfunctionsawaitpauses execution until Promise resolves- Use
try/catchfor 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
| Practice | Description | Example |
|---|---|---|
| Always await Promises | Don't forget await or .then() | const data = await fetch(url); |
| Use try/catch | Handle errors explicitly | try { ... } catch (e) { ... } |
| Parallel when possible | Use Promise.all for independent tasks | Promise.all([p1, p2]) |
| Return from async functions | Always return a value or Promise | return await getData(); |
| Use async/await over .then() | More readable, easier to debug | await func() vs func().then() |
| Add timeouts | Prevent hanging operations | withTimeout(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 awaitLogging 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
| Concept | ABAP | JavaScript |
|---|---|---|
| Execution | Always synchronous | Asynchronous by default |
| Database calls | SELECT (blocking) | await fetch() (non-blocking) |
| Error handling | TRY...CATCH...ENDTRY | try...catch (same concept!) |
| Parallel execution | RFC parallel processing | Promise.all() |
| Timeout | WAIT UP TO | Promise.race() with setTimeout |
| Return value | RETURN lv_value | return 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.
