I have a simple chat application written using FastAPI in Python and jQuery. The user enters a question into form, and a response is returned from the server. However, I also need to send the user message to a separate process that takes a long time (say, querying a database). I don't want the user to have to wait for that separate process to complete, but I have been unable to get that to work. I've tried all sorts of variations of Promise and await, but nothing works. Here is a toy example that demonstrates the problem:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
$('#message-form').submit(async function (event) {
event.preventDefault();
const input_message = $('#message-form input[name=message]').val()
$('#message-list').append('<li><strong>' + input_message + '</strong></li>');
side_track(input_message);
const response = await fetch('/submit_message', {
method: 'POST',
body: JSON.stringify({ message: input_message }),
headers: { 'Content-Type': 'application/json' },
});
// Reset the message input field
$('#message-form')[0].reset();
const newMessage = document.createElement('li');
$('#message-list').append(newMessage);
const stream = response.body;
const reader = stream.getReader();
const decoder = new TextDecoder();
const { value, done } = await reader.read();
const message = JSON.parse(decoder.decode(value)).message;
newMessage.innerHTML += message
});
});
async function side_track(question) {
const response = fetch('/side_track', {
method: 'POST',
body: JSON.stringify({ message: question }),
headers: { 'Content-Type': 'application/json' },
});
alert('Getting questions')
}
</script>
</head>
<body>
<ul id="message-list">
<li>Message list.</li>
<!-- Existing messages will be inserted here -->
</ul>
<form id="message-form" method="POST">
<input type="text" name="message" placeholder="Enter your message">
<button type="submit">Submit</button>
</form>
</body>
</html>
And the corresponding Python:
# -*- coding: utf-8 -*-
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from pydantic import BaseModel
from fastapi.staticfiles import StaticFiles
app = FastAPI()
templates = Jinja2Templates(directory="templates")
app.mount("/static", StaticFiles(directory="static"), name="static")
class MessageInput(BaseModel):
message: str
@app.post("/side_track")
async def side_track(message_data: MessageInput):
import time
time.sleep(10)
return {"status": "ok"}
@app.get("/", response_class=HTMLResponse)
async def index(request: Request):
return templates.TemplateResponse("index.html", {"request": request})
async def random_numbers():
import random
import asyncio
while True:
await asyncio.sleep(.1) # Wait for 1 second
yield random.randint(1, 10)
@app.post("/submit_message")
async def submit_message(message_data: MessageInput):
from fastapi.encoders import jsonable_encoder
async for number in random_numbers():
return jsonable_encoder({'message': number})
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=8000)
In the example above, the user has to wait the full 10 seconds for side_track
to complete before displaying the message returned from submit_message
. I want the message from submit_message
(which doesn't take any time to process) to display immediately, and the response from side_track
to be handled separately whenever it completes, without tying up the program.
EDIT: I modified the toy program to more accurately demonstrate the asynchronous generator that responds to submit_message
and make it easier to replicate the problem.