Async/Await Patterns
Master async/await patterns for handling asynchronous operations in React Native applications, including error handling and concurrent requests. This is a foundational concept in cross-platform mobile development that professional developers rely on daily. The explanations below are written to be beginner-friendly while covering the depth and nuance that comes from real-world React Native experience. Take your time with each section and practice the examples
Async/Await Basics
Async/await is a modern way to handle asynchronous operations in JavaScript. It makes asynchronous code look and behave more like synchronous code, making it easier to read and debug.. This is an essential concept that every React Native developer must understand thoroughly. In professional development environments, getting this right can mean the difference between code that works reliably and code that breaks in production. The following sections break this down into clear, digestible pieces with practical examples you can try immediately
Error Handling Patterns
- Try-catch blocks - Wrap async operations in try-catch
- Promise.all() - Handle multiple concurrent requests
- Promise.allSettled() - Handle multiple requests regardless of success/failure
- Promise.race() - Handle the first completed request
- Custom error boundaries - React error boundaries for async errors
Loading States
Always provide loading states for async operations to improve user experience. Use loading indicators, skeleton screens, or progress bars to show that data is being fetched.. This is an essential concept that every React Native developer must understand thoroughly. In professional development environments, getting this right can mean the difference between code that works reliably and code that breaks in production. The following sections break this down into clear, digestible pieces with practical examples you can try immediately
Async/Await Patterns Example
import React, { useState, useEffect } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, ActivityIndicator, Alert } from 'react-native';
// Mock API functions
const mockApi = {
fetchUser: async (id) => {
await new Promise(resolve => setTimeout(resolve, 1000)); // Simulate network delay
return { id, name: `User ${id}`, email: `user${id}@example.com` };
},
fetchPosts: async (userId) => {
await new Promise(resolve => setTimeout(resolve, 800));
return [
{ id: 1, title: `Post 1 by User ${userId}`, body: 'Post content...' },
{ id: 2, title: `Post 2 by User ${userId}`, body: 'Post content...' },
];
},
fetchComments: async (postId) => {
await new Promise(resolve => setTimeout(resolve, 600));
return [
{ id: 1, text: `Comment 1 on Post ${postId}` },
{ id: 2, text: `Comment 2 on Post ${postId}` },
];
},
};
// Sequential Loading Example
const SequentialLoading = () => {
const [user, setUser] = useState(null);
const [posts, setPosts] = useState([]);
const [comments, setComments] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const loadDataSequentially = async () => {
setLoading(true);
setError(null);
try {
// Load user first
const userData = await mockApi.fetchUser(1);
setUser(userData);
// Then load posts
const postsData = await mockApi.fetchPosts(userData.id);
setPosts(postsData);
// Finally load comments for first post
if (postsData.length > 0) {
const commentsData = await mockApi.fetchComments(postsData[0].id);
setComments(commentsData);
}
} catch (err) {
setError(err.message);
Alert.alert('Error', 'Failed to load data sequentially');
} finally {
setLoading(false);
}
};
return (
<View style={styles.section}>
<Text style={styles.sectionTitle}>Sequential Loading</Text>
<TouchableOpacity style={styles.button} onPress={loadDataSequentially}>
<Text style={styles.buttonText}>Load Data Sequentially</Text>
</TouchableOpacity>
{loading && <ActivityIndicator size="large" color="#2196F3" />}
{error && <Text style={styles.errorText}>{error}</Text>}
{user && (
<View style={styles.dataContainer}>
<Text style={styles.dataTitle}>User: {user.name}</Text>
<Text style={styles.dataSubtitle}>Email: {user.email}</Text>
</View>
)}
{posts.length > 0 && (
<View style={styles.dataContainer}>
<Text style={styles.dataTitle}>Posts ({posts.length})</Text>
{posts.map(post => (
<Text key={post.id} style={styles.dataItem}>• {post.title}</Text>
))}
</View>
)}
{comments.length > 0 && (
<View style={styles.dataContainer}>
<Text style={styles.dataTitle}>Comments ({comments.length})</Text>
{comments.map(comment => (
<Text key={comment.id} style={styles.dataItem}>• {comment.text}</Text>
))}
</View>
)}
</View>
);
};
// Concurrent Loading Example
const ConcurrentLoading = () => {
const [user, setUser] = useState(null);
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const loadDataConcurrently = async () => {
setLoading(true);
setError(null);
try {
// Load user and posts concurrently
const [userData, postsData] = await Promise.all([
mockApi.fetchUser(2),
mockApi.fetchPosts(2)
]);
setUser(userData);
setPosts(postsData);
} catch (err) {
setError(err.message);
Alert.alert('Error', 'Failed to load data concurrently');
} finally {
setLoading(false);
}
};
return (
<View style={styles.section}>
<Text style={styles.sectionTitle}>Concurrent Loading</Text>
<TouchableOpacity style={styles.button} onPress={loadDataConcurrently}>
<Text style={styles.buttonText}>Load Data Concurrently</Text>
</TouchableOpacity>
{loading && <ActivityIndicator size="large" color="#2196F3" />}
{error && <Text style={styles.errorText}>{error}</Text>}
{user && (
<View style={styles.dataContainer}>
<Text style={styles.dataTitle}>User: {user.name}</Text>
<Text style={styles.dataSubtitle}>Email: {user.email}</Text>
</View>
)}
{posts.length > 0 && (
<View style={styles.dataContainer}>
<Text style={styles.dataTitle}>Posts ({posts.length})</Text>
{posts.map(post => (
<Text key={post.id} style={styles.dataItem}>• {post.title}</Text>
))}
</View>
)}
</View>
);
};
// Error Handling Example
const ErrorHandling = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const loadDataWithErrorHandling = async () => {
setLoading(true);
setError(null);
try {
// Simulate an API call that might fail
const shouldFail = Math.random() > 0.5;
if (shouldFail) {
throw new Error('Simulated API error');
}
await new Promise(resolve => setTimeout(resolve, 1000));
setData({ message: 'Data loaded successfully!' });
} catch (err) {
setError(err.message);
console.error('API Error:', err);
} finally {
setLoading(false);
}
};
const retry = () => {
loadDataWithErrorHandling();
};
return (
<View style={styles.section}>
<Text style={styles.sectionTitle}>Error Handling</Text>
<TouchableOpacity style={styles.button} onPress={loadDataWithErrorHandling}>
<Text style={styles.buttonText}>Load Data (May Fail)</Text>
</TouchableOpacity>
{loading && <ActivityIndicator size="large" color="#2196F3" />}
{error && (
<View style={styles.errorContainer}>
<Text style={styles.errorText}>Error: {error}</Text>
<TouchableOpacity style={styles.retryButton} onPress={retry}>
<Text style={styles.retryButtonText}>Retry</Text>
</TouchableOpacity>
</View>
)}
{data && (
<View style={styles.dataContainer}>
<Text style={styles.dataTitle}>{data.message}</Text>
</View>
)}
</View>
);
};
// Main App
const App = () => {
return (
<View style={styles.container}>
<Text style={styles.title}>Async/Await Patterns</Text>
<SequentialLoading />
<ConcurrentLoading />
<ErrorHandling />
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
backgroundColor: '#f5f5f5',
},
title: {
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
marginBottom: 30,
color: '#333',
},
section: {
marginBottom: 30,
backgroundColor: 'white',
padding: 20,
borderRadius: 8,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
},
sectionTitle: {
fontSize: 20,
fontWeight: 'bold',
marginBottom: 15,
color: '#333',
},
button: {
backgroundColor: '#2196F3',
padding: 15,
borderRadius: 8,
alignItems: 'center',
marginBottom: 15,
},
buttonText: {
color: 'white',
fontSize: 16,
fontWeight: 'bold',
},
dataContainer: {
backgroundColor: '#f0f0f0',
padding: 15,
borderRadius: 8,
marginBottom: 10,
},
dataTitle: {
fontSize: 16,
fontWeight: 'bold',
color: '#333',
marginBottom: 5,
},
dataSubtitle: {
fontSize: 14,
color: '#666',
marginBottom: 5,
},
dataItem: {
fontSize: 14,
color: '#666',
marginBottom: 2,
},
errorText: {
color: '#f44336',
fontSize: 14,
textAlign: 'center',
marginBottom: 10,
},
errorContainer: {
alignItems: 'center',
padding: 20,
},
retryButton: {
backgroundColor: '#2196F3',
padding: 10,
borderRadius: 5,
},
retryButtonText: {
color: 'white',
fontSize: 14,
fontWeight: 'bold',
},
});
export default App;