Jak zaimplementować asynchroniczność w moim kodzie?

0

Witam,
długo już siedzę nad moim problemem, a rozwiązania znaleźć nie mogę. Tworzę program, który "przechodzi" po kilku zdjęciach, po czym wykrywa obiekty na nich się znajdujące. O ile na jednym zdjęciu radzi sobie świetnie, o tyle kiedy wrzucam to w pętlę, część kodu wykonuje się od razu, a część dopiero po czasie. Jest to spowodowane tym, że wykrycie obiektów na zdjęciach zajmuje trochę czasu. Jednak w mojej pętli console.log(imageNumber) wykonuje się jeden po drugim, a handleDetections potrzebuje na to więcej czasu, przez co wykrywane są obiekty tylko na ostatnim pliku. Jak mogę tu zaimplementować asynchroniczność, żeby działało jak chcę?

function filterDetections(detections){
    let count = {};
    for(const detection of detections) {
        console.log(detection);
        if(count[detection.label]){
            count[detection.label] += 1;
        }else {
            count[detection.label] = 1;
        }
    }
    return count;
}
function imageExists(image_url){
    var http = new XMLHttpRequest();
    http.open('HEAD', image_url, false);
    http.send();
    return http.status != 404;
}

let img;
let objectDetector;
let imageNumber = 0;
let imageDetections;
let JSONOutput = {};
let imageName;
let imageUrl;

async function setup(){
    objectDetector = ml5.objectDetector('cocossd');
    
    while(imageExists(`images/test${imageNumber}.jpg`)){
        await detectOnImage(`images/test${imageNumber}.jpg`);
        console.log(imageNumber);
        imageNumber++;
    }
}

function detectOnImage(url){
    imageName = `test${imageNumber}.jpg`;
    imageUrl = `images/test${imageNumber}.jpg`;
    img = loadImage(url, imageLoaded);
}

function imageLoaded(){
    console.log("imageLoaded");
    img.resize(0, 520);
    objectDetector.detect(img, handleDetections);
}

function handleDetections(err, results){
    if(err){
        console.error(err);
        return;
    }
    imageDetections = results;

    //JSON
    let saveObject = {
        detectoins: filterDetections(imageDetections)
    }
    JSONOutput[imageName] = saveObject;
}
3

Asynchroniczność w JS ma sens tylko wtedy kiedy czekasz na coś, np io, na plik, na request.

Jak masz proces który coś robi, np analizuje jakieś zdjęcie to asynchroniczność nie ma sensu w JS.

0

@TomRiddle: Też coś takiego mi przez głowę się przewinęło. Teraz już wiem. Więc jak mogę rozwiązać mój problem, żeby wszystko się wykonywało po kolei? Pownienem w każdej funkcji coś zwracać, a później na podstawie tego obsługiwać inne funkcje?

0

for(const detection of detections) {
console.log(detection);

Ile razy odpala się w sumie console.log? Na to też trzeba uważać, bo walenie console.logów w pętli też potrafi zamulić.

O ile na jednym zdjęciu radzi sobie świetnie, o tyle kiedy wrzucam to w pętlę, część kodu wykonuje się od razu, a część dopiero po czasie

No to możesz obrabiać tylko część obrazków. Zamiast od razu z 10 tysięcy, to tylko po 100 obrazków naraz (tylko przykład, liczby mogą być inne). Następnie dać setTimeout i kolejne 100 obrazków robić w kolejnej iteracji itp. Czyli finalnie obrabianie obrazków będzie dłużej trwać (bo sam będziesz dawać opóźnienia), za to użytkownik będzie miał cały czas kontrolę nad stroną. Nie będzie zamuły.

Taki prosty sposób bez bawienia się w wątki. Zakładając, że responsywność UI jest celem.

0

@LukeJL: Pętla jest uruchamiana 4 razy, bo testuję na właśnie 4 obrazkach. Nie do końca chodzi tu, żeby obrazki były przetwarzane jeden po drugim, ale kiedy używam zmiennej globalnej imageName w JSONOutput[imageName] = saveObject; to się ustawia na wartość z ostatniej iteracji. Może to wina zmiennych globalnych? Na użytkowniku mi nie zależy (jakkolwiek źle to nie zabrzmi), ponieważ program ma być do użytku własnego i ma mi pomóc jendnorazowo wykryć różne obiekty, a następnie ten JSON będzie służył w zupełnie innej aplikacji. Po prostu nie chce mi się tego ręczne robić.

0

co robi loadImage? To może zaburzać, jeśli wprowadza asynchroniczność.

objectDetector.detect(img, handleDetections);

Tutaj też potencjalnie(tego nie wiem) może być ukryta asynchroniczność.

Gouda105 napisał(a):

@LukeJL: Pętla jest uruchamiana 4 razy, bo testuję na właśnie 4 obrazkach. Nie do końca chodzi tu, żeby obrazki były przetwarzane jeden po drugim, ale kiedy używam zmiennej globalnej imageName w JSONOutput[imageName] = saveObject; to się ustawia na wartość z ostatniej iteracji.

Bo jakoś na dziko przekazujesz te dane.
Może zabrzmi to jak banał, ale funkcje w JS mogą zwracać wartości (również obiekty). Czemu nie napiszesz funkcji np. tak?

function detectOnImage(url){
    return {
      name: `test${imageNumber}.jpg`, 
      url: `images/test${imageNumber}.jpg`,
      img: loadImage(url, imageLoaded),
    };
}
//.....
const imageInfo = detectOnImage(`images/test${imageNumber}.jpg`); 
console.log(imageInfo); // masz obiekt z właściwościami name, url, img

i później to imageInfo możesz przekazywać do kolejnych funkcji albo wyciągać z niego konkretną właściwość np. imageInfo.name. Więc wcale nie potrzebujesz, żeby te dane były globalne.

EDIT: w sumie ten mój kod dalej jest nie taki, jak powinien być, bo korzysta ze zmiennych globalnych imageNumber choćby do wyciągania informacji (a to powinno być w argumentach przekazane). Musiałbyś cały flow mieć inny, no ale jak to rozwiązałeś, to nie chce mi się poprawiać tego. Ale chodziło mi bardziej o samo zwracanie informacji za pomocą return.

0

@LukeJL: Funkcja loadImage wczytuje obraz. https://p5js.org/reference/#/p5/loadImage
Zapomniałem wspomnieć, że korzystam z p5.js.

Problem udało mi się problem dzięki rekurencji. Czy to dobra praktyka?

0
Gouda105 napisał(a):

Problem udało mi się problem dzięki rekurencji

Tzn. w jaki konkretnie sposób?

0

@LukeJL: Może po prostu wkleję kod

function filterDetections(detections){
    let count = {};
    for(const detection of detections) {
        if(count[detection.label]){
            count[detection.label] += 1;
        }else {
            count[detection.label] = 1;
        }
    }
    return count;
}
function imageExists(image_url){
    var http = new XMLHttpRequest();
    http.open('HEAD', image_url, false);
    http.send();
    console.log(http.status);
    return http.status != 404;
}

let img;
let objectDetector;
let imageNumber = 0;
let imageDetections;
let JSONOutput = {};
let imageName;
let imageUrl;

async function setup(){
    objectDetector = ml5.objectDetector('cocossd');
    runDetection();
}

function detectOnImage(url){
    console.log(imageName);
    img = loadImage(url, imageLoaded);
}

function imageLoaded(){
    console.log("imageLoaded");
    img.resize(0, 520);
    objectDetector.detect(img, handleDetections);
}

function handleDetections(err, results){
    if(err){
        console.error(err);
        return;
    }
    imageDetections = results;

    //JSON
    let saveObject = {
        detectoins: filterDetections(imageDetections)
    }
    console.log(results);
    JSONOutput[imageName] = saveObject;

    if(imageExists(`images/test${imageNumber}.jpg`)){
        runDetection();
    }
}

function runDetection(){
    if(imageExists(`images/test${imageNumber}.jpg`)){
        imageName = `test${imageNumber}.jpg`;
        imageUrl = `images/test${imageNumber}.jpg`;
        detectOnImage(`images/test${imageNumber}.jpg`);
        console.log(imageNumber);
        imageNumber++;
    }
}
0

A gdzie jest ta rekurencja? Nie widzę. Chyba, że jakaś nie wprost, coś na zasadzie funkcja A wywołuje funkcje B, która wywołuje z powrotem funkcję A... Ale jeśli coś takiego tam jest, to nie sądzę, żeby to było dobre podejście i za chwilę pewnie znowu będziesz miał jakiś błąd.

A nie lepiej przepisać to po prostu, ale nie używając niepotrzebnie zmiennych globalnych? Wystarczy, że twoje funkcje będą przyjmować dane w argumentach i wypluwać je w return. Bo w tej chwili te funkcje niby są, ale tak jakby ich nie było. Masz strasznie zaciemniony kod. Podzieliłeś kod na funkcje, ale jest o dziwo mniej czytelny, niż jakby tam w ogóle nie było funkcji. Skoro każda funkcja robi co chce, bierze dane skąd chce, zapisuje dane też skąd chce...

0

https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event
https://developer.mozilla.org/en-US/docs/Web/API/Element/error_event

A nie możesz skorzystać z eventow load, oraz error do sprawdzania, czy obrazek został załadowany?

const img = document.querySelector('img');

img.addEventListener('load', () => {
  // tutaj kod po załadowaniu obrazka
});

img.addEventListener('error', () => {
  // tutaj kod jeśli nie udało się załadować obrazka
});

Kod wtedy znacznie by się uprościł i nie musiałbyś wymyślać całego mechanizmu sprawdzania od nowa.

1 użytkowników online, w tym zalogowanych: 0, gości: 1