JavaScript与Flask异步执行:如何让前端等待图片压缩后显示下载按钮?
图片压缩网站异步处理问题解决方案
技术问题
- 如何在JavaScript和Flask中执行异步代码?
- 如何让前端等待后端完成图片压缩后再显示下载按钮?
项目背景
基于Huffman编码搭建图片压缩网站,流程为:客户端上传图片→JS和Flask将图片POST到服务器→后端Python完成压缩→返回下载URL给客户端。
当前故障
图片可正常上传、压缩并存储,但点击压缩提交按钮后页面重载,无法等待后端返回URL并显示下载按钮。
现有代码
app.py
from flask import Flask, render_template, request, jsonify, send_from_directory import os from io import BytesIO import numpy as np import cv2 from copy import deepcopy as cp from concurrent.futures import ThreadPoolExecutor from flask_cors import CORS app = Flask(__name__) CORS(app) app.config["UPLOAD_FOLDER"] = "uploads" app.config["COMPRESSED_FOLDER"] = "compressed" app.config["OUTPUT_FOLDER"] = "output" executor = ThreadPoolExecutor() class Node: pass # class node class Image: pass # class image to compress image @app.route("/") def index(): return render_template("image_compressor.html") def process_image(file_path, file_name): image = Image() compressed_path = os.path.join( app.config["COMPRESSED_FOLDER"], f"compressed_{file_name}.bin" ) output_path = os.path.join( app.config["OUTPUT_FOLDER"], f"compressed_{file_name}.jpg" ) image.huffmanCode(file_path, compressed_path, output_path, toCheck=1) @app.route("/upload", methods=["POST"]) def upload_image(): if "image-upload" not in request.files: return jsonify({"error": "No file part"}), 400 file = request.files["image-upload"] if file.filename == "": return jsonify({"error": "No selected file"}), 400 if file: file_path = os.path.join(app.config["UPLOAD_FOLDER"], file.filename) file.save(file_path) file_name = file.filename.split(".")[0] executor.submit(process_image, file_path, file_name) return jsonify({"message": "Processing initiated", "filename": file.filename}) @app.route("/status/<filename>", methods=["GET"]) def status(filename): file_name = filename.split(".")[0] output_path = os.path.join( app.config["OUTPUT_FOLDER"], f"compressed_{file_name}.jpg" ) if os.path.exists(output_path): return jsonify({"status": "done", "url": f"/download/{filename}"}) else: return jsonify({"status": "processing"}) @app.route("/download/<filename>", methods=["GET"]) def download_file(filename): file_name = filename.split(".")[0] output_path = os.path.join( app.config["OUTPUT_FOLDER"], f"compressed_{file_name}.jpg" ) if os.path.exists(output_path): return send_from_directory( directory=app.config["OUTPUT_FOLDER"], path=f"compressed_{file_name}.jpg" ) else: return "File not found", 404 if __name__ == "__main__": for folder in [ app.config["UPLOAD_FOLDER"], app.config["COMPRESSED_FOLDER"], app.config["OUTPUT_FOLDER"], ]: if not os.path.exists(folder): os.makedirs(folder) app.run(debug=True)
JavaScript代码
document.getElementById('uploadForm').addEventListener('submit', function (event) { event.preventDefault(); // Prevent the default form submission var formData = new FormData(this); var uploadButton = event.target.querySelector('button[type="submit"]'); uploadButton.textContent = "Uploading..."; // Indicate upload progress uploadButton.disabled = true; console.log('http://127.0.0.1:5000/upload') fetch('http://127.0.0.1:5000/upload', { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.error) { throw new Error(data.error); } var filename = data.filename; checkStatus(filename, uploadButton); }) .catch(error => { console.error('Error:', error); alert('There was an error processing your request.'); uploadButton.textContent = "Compress"; uploadButton.disabled = false; }); }); function checkStatus(filename, uploadButton) { console.log(`http://127.0.0.1:5000/status/${filename}`) fetch(`http://127.0.0.1:5000/status/${filename}`) .then(response => response.json()) .then(data => { if (data.status === "done") { var downloadLink = document.getElementById('downloadLink'); var downloadButton = document.getElementById('downloadButton'); downloadButton.href = data.url; downloadButton.download = filename; downloadLink.style.display = 'block'; uploadButton.textContent = "Compress"; uploadButton.disabled = false; } else { setTimeout(() => checkStatus(filename, uploadButton), 2000); // Poll every 2 seconds } }) .catch(error => { console.error('Error:', error); alert('There was an error processing your request.'); uploadButton.textContent = "Compress"; uploadButton.disabled = false; }); }
HTML代码
<section class="container compressor"> <div class="background"> <div class="heading_wrapper"> <h1 class="heading">Fast & efficient image <span class="highlight">compression</span></h1> </div> <form class="upload_wrapper" id="uploadForm"> <div class="upload_presentation"> <input class="upload" id="image-upload" type="file" name="image-upload" accept=".jpg, .png" /> <label for="image-upload"> <i class="fa-solid fa-upload upload_icon"></i> <span>Choose a file</span> </label> </div> <div class="button_wrapper"> <button type="submit" class="btn btn_compress">Compress</button> </div> <div id="downloadLink" style="display:none;"> <a id="downloadButton" class="btn btn_download" href="#" download>Download Compressed Image</a> </div> </form> </div> </section>
问题分析与修复方案
1. 页面重载问题根源
JS代码中event.preventDefault()未生效,原因是DOM未完全加载时JS代码已执行,导致document.getElementById('uploadForm')无法找到目标元素,事件监听器未成功绑定,点击提交按钮时触发表单默认提交行为,造成页面重载。
2. 修复步骤
(1)确保JS代码在DOM加载完成后执行
将JS代码包裹在DOMContentLoaded事件监听中,或把JS脚本放在HTML页面的末尾(</body>标签前):
document.addEventListener('DOMContentLoaded', function() { document.getElementById('uploadForm').addEventListener('submit', function (event) { // 原submit事件处理代码 }); function checkStatus(filename, uploadButton) { // 原checkStatus函数代码 } });
(2)优化Fetch请求路径
将硬编码的http://127.0.0.1:5000改为相对路径,避免环境切换时的路径错误:
// 替换上传请求路径 fetch('/upload', { method: 'POST', body: formData }) // 替换状态查询请求路径 fetch(`/status/${filename}`)
3. 异步代码执行说明
Flask后端异步实现
使用ThreadPoolExecutor将图片压缩任务process_image提交到线程池执行,避免阻塞主请求线程,保证服务器能处理其他请求:
executor.submit(process_image, file_path, file_name)
这是Flask中实现轻量级异步任务的常用方式,适合IO密集型或计算密集型的后台任务。
前端异步实现
- 用
fetch发起异步POST请求上传图片,无需等待同步响应; - 通过
setTimeout定时轮询/status接口,检查压缩任务状态; - 当状态返回
done时,动态显示下载按钮并绑定下载URL。
这种轮询方式简单可靠,适合小型项目的异步状态同步需求。
内容的提问来源于stack exchange,提问作者dinhsang2313
相关产品推荐
相关产品推荐

