Keywords: JavaScript | Promise | Asynchronous Programming | pending state | async/await
Abstract: This article provides an in-depth exploration of why Promise objects return <pending> state in JavaScript, analyzing the Promise/A+ specification, asynchronous function execution mechanisms, and practical code examples. It systematically explains proper Promise chaining, async/await syntax, and methods to avoid common asynchronous programming pitfalls, offering complete solutions from basic concepts to advanced practices.
Understanding Promise Asynchronous Execution Mechanism
In JavaScript asynchronous programming, Promise objects serve as the core abstraction for modern asynchronous handling. When developers encounter Promise { <pending> } output, this typically indicates that the Promise has not yet completed its asynchronous operation. Understanding this phenomenon requires examining the execution mechanism of Promises.
Principles of Promise State Transitions
According to the Promise/A+ specification, Promises exist in three states: pending (in progress), fulfilled (successfully completed), and rejected (failed). State transitions are irreversible and can only proceed from pending to either fulfilled or rejected. In the initial code example:
let AuthUser = data => {
return google.login(data.username, data.password).then(token => { return token } )
}
let userToken = AuthUser(data)
console.log(userToken) // Output: Promise { <pending> }
Here, userToken immediately outputs the pending state because google.login is an asynchronous operation that requires time to complete the authentication process. At this point, the Promise remains in the pending state and has not yet obtained the final result.
Correct Methods for Retrieving Promise Results
To obtain the final result of a Promise, callback functions must be registered through the .then() method:
let AuthUser = function(data) {
return google.login(data.username, data.password).then(token => { return token } )
}
let userToken = AuthUser(data)
userToken.then(function(result) {
console.log(result) // Outputs actual user token
})
This design ensures the sequential nature of asynchronous operations, where corresponding callback functions execute only when the Promise state changes to fulfilled.
Promise Chaining Mechanism
Promise chaining represents one of its most powerful features. According to the specification, when a .then() handler returns a value, that value becomes the resolution value of the new Promise; if it returns another Promise, the original Promise waits for that Promise to resolve before continuing.
function initPromise() {
return new Promise(function(res, rej) {
res("initResolve");
})
}
// Example 1: Returning a regular value
initPromise()
.then(function(result) {
console.log(result); // "initResolve"
return "normalReturn";
})
.then(function(result) {
console.log(result); // "normalReturn"
});
// Example 2: Returning another Promise
initPromise()
.then(function(result) {
console.log(result); // "initResolve"
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve("secondPromise");
}, 1000)
})
})
.then(function(result) {
console.log(result); // "secondPromise"
});
Underlying Implementation of async/await Syntax Sugar
async/await represents syntax sugar built on top of Promises, providing a more intuitive asynchronous programming experience. The referenced article demonstrates this feature:
async function latestTime() {
const bl = await web3.eth.getBlock('latest');
console.log(bl.timestamp); // Returns primitive value
return bl.timestamp;
}
const time = latestTime(); // Still returns Promise { <pending> }
Although async functions internally use the await keyword to "block" execution, async functions themselves always return a Promise. This explains why directly calling an async function still yields a pending Promise.
Promise Handling in Fetch API
Reference Article 1 demonstrates similar issues encountered with the Fetch API:
async function asyncFunc() {
const response = await fetch('/target.php');
return response.json();
}
function btnFunc() {
output = asyncFunc().then(response => console.log(response));
console.log(output); // Promise { <state>: "pending" }
}
The key insight here is understanding that response.json() itself represents another asynchronous operation that returns a Promise. Therefore, the entire asynchronous chain requires appropriate handling.
Best Practices and Error Handling
In practical development, adopting unified asynchronous handling patterns is recommended:
// Approach 1: Using async/await
async function getUserToken(data) {
try {
const token = await AuthUser(data);
console.log(token);
// Subsequent processing
} catch (error) {
console.error('Authentication failed:', error);
}
}
// Approach 2: Using Promise chains
AuthUser(data)
.then(token => {
console.log(token);
return processToken(token);
})
.then(processedToken => {
// Further processing
})
.catch(error => {
console.error('Error during processing:', error);
});
Conclusion and Future Outlook
Understanding the pending state of Promises is crucial for mastering JavaScript asynchronous programming. Through proper use of .then() chaining, async/await syntax, and appropriate error handling mechanisms, developers can build robust asynchronous applications. As the JavaScript language evolves, new asynchronous patterns like Top-level await are emerging, but Promise remains a fundamental abstraction.