Skillversitas

Promises

A Promise is an object that represents the result (success or failure) of a future asynchronous operation. A Promise has three states: pending (not yet finished), fulfilled (succeeded), and rejected (failed).

Code Example

Basic Promises

// Create a Promise
const fetchStudentData = new Promise((resolve, reject) => {
  // Simulated asynchronous operation
  setTimeout(() => {
    const successful = true;

    if (successful) {
      resolve({
        studentId: '2024001',
        name: 'Mike Johnson',
        major: 'Computer Science'
      });
    } else {
      reject(new Error('Failed to fetch student data'));
    }
  }, 1000);
});

// Use the Promise
fetchStudentData
  .then(data => {
    console.log('Data:', data);
  })
  .catch(error => {
    console.error('Error:', error.message);
  });

then(), catch(), finally()

const processGrade = new Promise((resolve, reject) => {
  setTimeout(() => {
    const grade = Math.floor(Math.random() * 100);
    resolve(grade);
  }, 500);
});

processGrade
  .then(grade => {
    console.log('Grade:', grade);
    return grade + 10; // the returned value becomes the input for the next then
  })
  .then(finalGrade => {
    console.log('Final grade:', finalGrade);
  })
  .catch(error => {
    console.error('Error:', error);
  })
  .finally(() => {
    console.log('Processing complete'); // always executed
  });

Code Explanation

Promise.all()

All Promises must resolve. If one fails, they all fail.

const p1 = fetch('/api/student').then(r => r.json());
const p2 = fetch('/api/lecturer').then(r => r.json());
const p3 = fetch('/api/course').then(r => r.json());

Promise.all([p1, p2, p3])
  .then(([student, lecturer, course]) => {
    console.log('Student:', student.length);
    console.log('Lecturer:', lecturer.length);
    console.log('Courses:', course.length);
  })
  .catch(error => {
    console.error('One failed:', error);
  });

Promise.allSettled()

All Promises run until completion. No rejection cancels the rest.

const tasks = [
  Promise.resolve('Data A successful'),
  Promise.reject('Data B failed'),
  Promise.resolve('Data C successful')
];

Promise.allSettled(tasks)
  .then(results => {
    results.forEach((result, index) => {
      if (result.status === 'fulfilled') {
        console.log(`Task ${index}: OK -`, result.value);
      } else {
        console.log(`Task ${index}: FAILED -`, result.reason);
      }
    });
  });
// Output:
// Task 0: OK - Data A successful
// Task 1: FAILED - Data B failed
// Task 2: OK - Data C successful

Promise.race()

Whichever resolves/rejects first is the one that is taken.

const serverIndo = fetch('https://api-id.university.edu/data');
const serverSg = fetch('https://api-sg.university.edu/data');

Promise.race([serverIndo, serverSg])
  .then(response => {
    // The response from the faster server
    console.log('Server tercepat:', response.url);
    return response.json();
  })
  .catch(error => {
    console.error('All servers timeout:', error);
  });

Promise.any()

Take the one that resolves first. Only reject if all of them reject.

const mirror1 = fetch('https://mirror1.university.edu/data');
const mirror2 = fetch('https://mirror2.university.edu/data');
const mirror3 = fetch('https://mirror3.university.edu/data');

Promise.any([mirror1, mirror2, mirror3])
  .then(response => {
    // The response from the mirror that resolved first
    console.log('Result from:', response.url);
    return response.json();
  })
  .catch(error) {
    // All mirrors failed
    console.error('All mirrors failed:', error.errors);
  }

Real-World Examples

Parallel Fetch of Student & Lecturer Data

async function loadDashboard() {
  try {
    const [studentRes, lecturerRes, statsRes] = await Promise.all([
      fetch('/api/student?page=1&limit=10'),
      fetch('/api/lecturer?page=1&limit=10'),
      fetch('/api/statistics')
    ]);

    const [student, lecturer, stats] = await Promise.all([
      studentRes.json(),
      lecturerRes.json(),
      statsRes.json()
    ]);

    renderStudentTable(student);
    renderFacultyTable(lecturer);
    renderStatistics(stats);
  } catch (error) {
    showError('Failed to load dashboard:', error);
  }
}

Fallback with Promise.any

// Try several mirror servers
async function fetchDataWithFallback() {
  const mirrors = [
    'https://api1.university.edu/student',
    'https://api2.university.edu/student',
    'https://api3.university.edu/student'
  ];

  const promises = mirrors.map(url =>
    fetch(url).then(r => {
      if (!r.ok) throw new Error(`Status ${r.status}`);
      return r.json();
    })
  );

  try {
    const data = await Promise.any(promises);
    return data;
  } catch (error) {
    console.error('All mirrors failed:', error.errors);
    throw new Error('No server available');
  }
}

Retry Mechanism

function fetchWithRetry(url, maxRetries = 3) {
  return new Promise((resolve, reject) => {
    let attempts = 0;

    function attempt() {
      attempts++;
      fetch(url)
        .then(response => {
          if (!response.ok && attempts < maxRetries) {
            setTimeout(attempt, 1000 * attempts); // exponential backoff
            return;
          }
          resolve(response);
        })
        .catch(error => {
          if (attempts < maxRetries) {
            setTimeout(attempt, 1000 * attempts);
          } else {
            reject(error);
          }
        });
    }

    attempt();
  });
}

Loading States with Promises

let loadingPromises = [];

function showLoading(promise, label = 'Memprocess...') {
  const loadingEl = document.createElement('div');
  loadingEl.className = 'loading-overlay';
  loadingEl.innerHTML = `<div class="spinner"></div><p>${label}</p>`;
  document.body.appendChild(loadingEl);

  loadingPromises.push(
    promise.finally(() => {
      loadingEl.remove();
    })
  );
}

// Usage
const savePromise = fetch('/api/save', { method: 'POST', body: data });
showLoading(savePromise, 'Menyimpan data...');
savePromise.then(() => showNotification('Tersave!'));

Promises vs Callback Hell

// Callback hell — hard to read
fetchStudent(studentId, function (student) {
  fetchGrade(student.studentId, function (grade) {
    fetchSummary(grade, function (summary) {
      console.log(summary);
    }, handleError);
  }, handleError);
}, handleError);

// Promises — clean and readable
fetchStudent(studentId)
  .then(student => fetchGrade(student.studentId))
  .then(grade => fetchSummary(grade))
  .then(summary => console.log(summary))
  .catch(handleError);

Conclusion

Promises turn callback-based code into more readable code. Use Promise.all() for parallel operations that must all succeed, Promise.allSettled() for independent parallel operations, Promise.race() for timeouts/fallbacks, and Promise.any() for redundant requests. Use .catch() and .finally() for error handling and cleanup.