Jak zaimplementować asynchroniczność w moim kodzie?

Jak zaimplementować asynchroniczność w moim kodzie?
Gouda105
  • Rejestracja:ponad 7 lat
  • Ostatnio:około 2 godziny
  • Postów:487
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ę?

Kopiuj
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;
}
Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:5 minut
  • Lokalizacja:Laska, z Polski
  • Postów:10056
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.

Gouda105
  • Rejestracja:ponad 7 lat
  • Ostatnio:około 2 godziny
  • Postów:487
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?

LukeJL
  • Rejestracja:około 11 lat
  • Ostatnio:2 minuty
  • Postów:8399
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.


edytowany 2x, ostatnio: LukeJL
Gouda105
  • Rejestracja:ponad 7 lat
  • Ostatnio:około 2 godziny
  • Postów:487
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ć.

edytowany 1x, ostatnio: Gouda105
LukeJL
  • Rejestracja:około 11 lat
  • Ostatnio:2 minuty
  • Postów:8399
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?

Kopiuj
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.


edytowany 6x, ostatnio: LukeJL
Gouda105
  • Rejestracja:ponad 7 lat
  • Ostatnio:około 2 godziny
  • Postów:487
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?

LukeJL
  • Rejestracja:około 11 lat
  • Ostatnio:2 minuty
  • Postów:8399
0
Gouda105 napisał(a):

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

Tzn. w jaki konkretnie sposób?


Gouda105
  • Rejestracja:ponad 7 lat
  • Ostatnio:około 2 godziny
  • Postów:487
0

@LukeJL: Może po prostu wkleję kod

Kopiuj
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++;
    }
}
LukeJL
  • Rejestracja:około 11 lat
  • Ostatnio:2 minuty
  • Postów:8399
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...


edytowany 1x, ostatnio: LukeJL
Xarviel
  • Rejestracja:ponad 3 lata
  • Ostatnio:około 2 godziny
  • Postów:847
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?

Kopiuj
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.

edytowany 1x, ostatnio: Xarviel
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)