You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaScript与Flask异步执行:如何让前端等待图片压缩后显示下载按钮?

图片压缩网站异步处理问题解决方案

技术问题

  1. 如何在JavaScript和Flask中执行异步代码?
  2. 如何让前端等待后端完成图片压缩后再显示下载按钮?

项目背景

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 08:27:05