我正在 Express.js 应用程序中实现 API 调用,并注意到 axios 和 fetch 之间的不同行为:
具体来说,在我的原始代码中的 Express 服务器代码中,当使用 fetch 时,没有
const data = response.json()
的 await
返回未定义的对象,并且无法正常工作。但是,使用 axios,即使不使用 const data = response.data;
,await
也能正确检索数据对象。
(axios 与 fetch)
// axios
const response = await axios.get(url);
const data = response.data; // works
// const data = await response.data; // works
// fetch
// const response = await fetch(url);
// const data = response.json(); // doesn't work, return an undefined object
// const data = await response.json(); // works
我尝试实现这4种数据检索模式,并确认3种模式肯定有效,但作为技术知识,我想了解为什么不带await的fetch实现不起作用的根本原因。
[原始代码,用于重现行为] (服务器.js)
import express from 'express';
import bodyParser from 'body-parser';
import path from 'path';
import axios from 'axios';
const app = express();
const API_KEY = '3e11847ddf2fbb92d4f9bc989360f058';
const PORT = 3007;
app.use(express.static('public'));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
const __dirname = path.resolve();
// Genre IDs for TMDB API
const genres = {
'action': 28,
'comedy': 35,
'drama': 18,
'horror': 27,
'romance': 10749,
'science fiction': 878
};
async function searchMovies(genre, year, rating) {
try {
const baseUrl = `https://api.themoviedb.org/3/discover/movie`;
const queryParams = new URLSearchParams({
api_key: API_KEY,
with_genres: genres[genre],
primary_release_year: year,
'vote_average.gte': rating,
sort_by: 'vote_average.desc',
page: 1
});
const url = `${baseUrl}?${queryParams.toString()}`;
console.log(url);
// axios
const response = await axios.get(url);
// both work
const data = response.data; // works
// const data = await response.data; // works
// fetch
// const response = await fetch(url);
// const data = response.json(); // doesn't work, return an undefined object
// const data = await response.json(); // works
if (data.total_results === 0) {
throw new Error('No movies found matching your criteria');
}
return data.results.slice(0, 10); // Return top 10 results
} catch (error) {
throw new Error(error.response?.data?.message || error.message);
}
}
app.get('/', (req, res) => {
res.render(__dirname + '/movies.ejs', {
genres: Object.keys(genres),
currentYear: new Date().getFullYear()
});
});
app.post('/', async (req, res) => {
try {
const { genre, year, rating } = req.body;
// Validation
if (!genre || !year || !rating) {
throw new Error('All fields are required');
}
if (year < 1900 || year > new Date().getFullYear()) {
throw new Error('Invalid year');
}
if (rating < 0 || rating > 10) {
throw new Error('Rating must be between 0 and 10');
}
const movies = await searchMovies(genre, year, rating);
res.render(__dirname + '/movies.ejs', {
genres: Object.keys(genres),
currentYear: new Date().getFullYear(),
movies: movies,
searchParams: { genre, year, rating }
});
} catch (error) {
res.render(__dirname + '/movies.ejs', {
genres: Object.keys(genres),
currentYear: new Date().getFullYear(),
error: error.message,
searchParams: req.body
});
}
});
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
(电影.ejs)
<!DOCTYPE html>
<html>
<head>
<title>Movie Search</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="main.css">
</head>
<body>
<div class="container">
<h1>Movie Search</h1>
<form class="search-form" action="/" method="POST">
<div class="form-group">
<label for="genre">Genre:</label>
<select name="genre" id="genre" required>
<option value="">Select a genre</option>
<% genres.forEach(genre => { %>
<option value="<%= genre %>"
<%= locals.searchParams?.genre === genre ? 'selected' : '' %>>
<%= genre.charAt(0).toUpperCase() + genre.slice(1) %>
</option>
<% }) %>
</select>
</div>
<div class="form-group">
<label for="year">Release Year:</label>
<input type="number"
name="year"
id="year"
min="1900"
max="<%= currentYear %>"
value="<%= locals.searchParams?.year || currentYear %>"
required>
</div>
<div class="form-group">
<label for="rating">Minimum Rating (0-10):</label>
<input type="number"
name="rating"
id="rating"
min="0"
max="10"
step="0.1"
value="<%= locals.searchParams?.rating || 7.0 %>"
required>
</div>
<button type="submit">Search Movies</button>
</form>
<% if (locals.error) { %>
<div class="error"><%= error %></div>
<% } %>
<% if (locals.movies) { %>
<h2>Search Results</h2>
<div class="movie-grid">
<% movies.forEach(movie => { %>
<div class="movie-card">
<img class="movie-poster"
src="https://image.tmdb.org/t/p/w500<%= movie.poster_path %>"
alt="<%= movie.title %> poster">
<div class="movie-info">
<h3 class="movie-title"><%= movie.title %></h3>
<p><strong>Rating:</strong> <%= movie.vote_average %>/10
(<%= movie.vote_count %> votes)</p>
<p><strong>Release Date:</strong>
<%= new Date(movie.release_date).toLocaleDateString() %></p>
<p><%= movie.overview %></p>
</div>
</div>
<% }) %>
</div>
<% } %>
</div>
</body>
</html>
(仅供参考,/public/main.css)
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
font-family: Arial, sans-serif;
}
.search-form {
background: #f5f5f5;
padding: 20px;
border-radius: 8px;
margin-bottom: 30px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.form-group select,
.form-group input {
width: 100%;
padding: 8px;
border-radius: 4px;
border: 1px solid #ddd;
}
.movie-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
}
.movie-card {
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
background: white;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.movie-poster {
width: 100%;
height: 400px;
object-fit: cover;
}
.movie-info {
padding: 15px;
}
.movie-title {
font-size: 1.2em;
margin: 0 0 10px 0;
}
.error {
color: red;
padding: 10px;
background: #ffebee;
border-radius: 4px;
margin-bottom: 20px;
}
button {
background: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background: #45a049;
}