it-swarm-eu.dev

Jak vrátím odpověď z asynchronního volání?

Mám funkci foo, která dělá žádost Ajax. Jak mohu vrátit odpověď z foo?

Snažil jsem se vrátit hodnotu z zpětného volání success a také přiřadit odpověď místní proměnné uvnitř funkce a vrátit ji zpět, ale žádný z těchto způsobů skutečně odpověď nevrátí.

function foo() {
    var result;

    $.ajax({
        url: '...',
        success: function(response) {
            result = response;
            // return response; // <- I tried that one as well
        }
    });

    return result;
}

var result = foo(); // It always ends up being `undefined`.
4904
Felix Kling

→ Pro obecnější vysvětlení asynchronního chování s různými příklady viz.Proč je moje proměnná nezměněná poté, co jsem ji změnila uvnitř funkce? - Asynchronní odkaz na kód

→ Pokud problém již porozumíte, přejděte na níže uvedená možná řešení.

Problém

Ain Ajax stojí pro asynchronní . To znamená, že odeslání požadavku (nebo spíše přijetí odpovědi) je vyřazeno z běžného prováděcího toku. Ve vašem příkladu se $.ajax vrací okamžitě a další příkaz, return result;, se provede před tím, než byla funkce, kterou jste předali jako zpětné volání success, nazvána.

Zde je analogie, která doufá, že rozdíl mezi synchronním a asynchronním tokem je jasnější:

Synchronní

Představte si, že zavoláte příteli a požádáte ho, aby pro vás něco hledal. I když to může chvíli trvat, počkejte na telefonu a zírajte do vesmíru, dokud vám přítel nepředá odpověď, kterou potřebujete.

Totéž se děje při volání funkce obsahující "normální" kód:

function findItem() {
    var item;
    while(item_not_found) {
        // search
    }
    return item;
}

var item = findItem();

// Do something with item
doSomethingElse();

Přestože findItem může trvat dlouho, spuštění jakéhokoliv kódu po var item = findItem(); musí počkat dokud funkce nevrátí výsledek.

Asynchronní

Zavoláte znovu svému příteli ze stejného důvodu. Tentokrát mu ale řeknete, že jste ve spěchu a měl by zavolej zpět do vašeho mobilního telefonu. Zavěsíš, opustíš dům a uděláš co chceš. Jakmile vám váš přítel zavolá zpět, jedná se o informace, které vám dal.

To je přesně to, co se děje, když děláte žádost Ajax.

findItem(function(item) {
    // Do something with item
});
doSomethingElse();

Namísto čekání na odpověď pokračuje provádění okamžitě a příkaz po provedení volání Ajax. Chcete-li získat odpověď nakonec, poskytnete funkci, která má být volána, jakmile je odpověď přijata, zpětné volání (všimněte si něco? zpětné volání?). Jakýkoliv příkaz přicházející po tomto volání je proveden před voláním zpětného volání.


Řešení (y)

Přijmout asynchronní povahu JavaScriptu! Zatímco některé asynchronní operace poskytují synchronní protějšky (tak i "Ajax"), je obvykle odradeno je používat, zejména v kontextu prohlížeče.

Proč je to špatné, ptáš se?

JavaScript běží ve vlákně uživatelského rozhraní prohlížeče a jakýkoli dlouhodobě probíhající proces uzamkne uživatelské rozhraní, takže je nereaguje. Kromě toho existuje horní limit doby provádění JavaScriptu a prohlížeč se zeptá uživatele, zda má pokračovat v provádění nebo ne.

To vše je opravdu špatný uživatelský zážitek. Uživatel nebude schopen zjistit, zda vše funguje dobře nebo ne. Účinek bude navíc horší pro uživatele s pomalým připojením.

V následujícím textu se podíváme na tři různá řešení, která jsou postavena na sobě:

  • Promises s async/await(ES2017 +, dostupné ve starších prohlížečích, pokud používáte transpiler nebo regenerátor)
  • zpětná volání (populární v uzlu)
  • Promises s then()(ES2015 +, dostupné ve starších prohlížečích, pokud používáte jednu z mnoha slibných knihoven)

Všechny tři jsou dostupné v aktuálních prohlížečích a uzlu 7+.


ES2017 +: Promises with async/await

Verze ECMAScript vydaná v roce 2017 představila podpora na úrovni syntaxe pro asynchronní funkce. Pomocí async a await můžete psát asynchronně v "synchronním stylu". Kód je stále asynchronní, ale je snazší číst/porozumět.

async/await navazuje na sliby: funkce async vždy vrátí slib. await "rozbalí" slib a buď vyústí v hodnotu, kterou byl slib vyřešen, nebo vyvolá chybu, pokud byl slib odmítnut.

Důležité: Můžete použít pouze await uvnitř funkce async. Právě teď await na nejvyšší úrovni ještě není podporováno, takže možná budete muset provést asynchronní IIFE ( Immedantly Invoked Function Expression ), abyste spustili async kontext.

Další informace o async a await na MDN.

Zde je příklad, který navazuje na zpoždění výše:

// Using 'superagent' which will return a promise.
var superagent = require('superagent')

// This is isn't declared as `async` because it already returns a promise
function delay() {
  // `delay` returns a promise
  return new Promise(function(resolve, reject) {
    // Only `delay` is able to resolve or reject the promise
    setTimeout(function() {
      resolve(42); // After 3 seconds, resolve the promise with value 42
    }, 3000);
  });
}


async function getAllBooks() {
  try {
    // GET a list of book IDs of the current user
    var bookIDs = await superagent.get('/user/books');
    // wait for 3 seconds (just for the sake of this example)
    await delay();
    // GET information about each book
    return await superagent.get('/books/ids='+JSON.stringify(bookIDs));
  } catch(error) {
    // If any of the awaited promises was rejected, this catch block
    // would catch the rejection reason
    return null;
  }
}

// Start an IIFE to use `await` at the top level
(async function(){
  let books = await getAllBooks();
  console.log(books);
})();

Aktuální prohlížeč a node version support async/await. Můžete také podporovat starší prostředí transformací kódu na ES5 pomocí regenerátoru (nebo nástrojů, které používají regenerátor, například Babel ).


Povolit funkce zpětná volání

Zpětné volání je jednoduše funkce předaná jiné funkci. Tato jiná funkce může funkci předat, kdykoliv je připravena. V kontextu asynchronního procesu bude zpětné volání voláno při každém asynchronním procesu. Obvykle je výsledek předán zpětnému volání.

V příkladu otázky můžete foo přijmout zpětné volání a použít jej jako success zpětné volání. Takže tohle

var result = foo();
// Code that depends on 'result'

se stává

foo(function(result) {
    // Code that depends on 'result'
});

Zde jsme definovali funkci "inline", ale můžete předat libovolný odkaz na funkci:

function myCallback(result) {
    // Code that depends on 'result'
}

foo(myCallback);

foo samotné je definováno následovně:

function foo(callback) {
    $.ajax({
        // ...
        success: callback
    });
}

callback bude odkazovat na funkci, kterou předáváme foo, když ji voláme a my ji jednoduše předáme success. Tj. jakmile je požadavek Ajax úspěšný, $.ajax zavolá callback a předá odpověď zpětnému volání (které lze označit result, protože takto jsme definovali zpětné volání).

Odpověď můžete také zpracovat před odesláním zpětnému volání:

function foo(callback) {
    $.ajax({
        // ...
        success: function(response) {
            // For example, filter the response
            callback(filtered_response);
        }
    });
}

Je jednodušší psát kód pomocí zpětných volání, než se může zdát. Konec konců, JavaScript v prohlížeči je silně událost-řízený (DOM události). Příjem odpovědi Ajax není nic jiného než událost.
Obtíže by mohly vzniknout, pokud budete muset pracovat s kódem třetí strany, ale většina problémů může být vyřešena pouhým přemýšlením prostřednictvím aplikačního toku.


ES2015 +: Promises with then ()

Promise API je nová funkce ECMAScript 6 (ES2015), ale má dobrou podporu prohlížeče již. Existuje také mnoho knihoven, které implementují standardní Promises API a poskytují další metody pro usnadnění použití a složení asynchronních funkcí (např. bluebird ).

Sliby jsou kontejnery pro budoucí hodnoty. Když slib obdrží hodnotu (je vyřešeno) nebo když je zrušeno (odmítnuto), oznámí všem svým "posluchačům", kteří chtějí k této hodnotě přistupovat.

Výhodou oproti prostému zpětnému volání je, že vám umožňují oddělit kód a je snazší je skládat.

Zde je jednoduchý příklad použití příslibu:

function delay() {
  // `delay` returns a promise
  return new Promise(function(resolve, reject) {
    // Only `delay` is able to resolve or reject the promise
    setTimeout(function() {
      resolve(42); // After 3 seconds, resolve the promise with value 42
    }, 3000);
  });
}

delay()
  .then(function(v) { // `delay` returns a promise
    console.log(v); // Log the value once it is resolved
  })
  .catch(function(v) {
    // Or do something else if it is rejected 
    // (it would not happen in this example, since `reject` is not called).
  });

Na výzvu Ajaxu bychom mohli použít takovéto sliby:

function ajax(url) {
  return new Promise(function(resolve, reject) {
    var xhr = new XMLHttpRequest();
    xhr.onload = function() {
      resolve(this.responseText);
    };
    xhr.onerror = reject;
    xhr.open('GET', url);
    xhr.send();
  });
}

ajax("/echo/json")
  .then(function(result) {
    // Code depending on result
  })
  .catch(function() {
    // An error occurred
  });

Popis všech výhod, které tato nabídka nabízí, je nad rámec této odpovědi, ale pokud píšete nový kód, měli byste je vážně zvážit. Poskytují velkou abstrakci a oddělení kódu.

Další informace o slibech: HTML5 skály - Sliby JavaScriptu

Boční poznámka: jQuery odložené objekty

Odložené objekty jsou jQuery vlastní implementací slibů (před standardizací API Promise). Chová se téměř jako sliby, ale vystavuje mírně odlišné API.

Každá Ajax metoda jQuery již vrací "odložený objekt" (vlastně slib odloženého objektu), který můžete jednoduše vrátit z vaší funkce:

function ajax() {
    return $.ajax(...);
}

ajax().done(function(result) {
    // Code depending on result
}).fail(function() {
    // An error occurred
});

Boční poznámka: Slib gotchas

Mějte na paměti, že sliby a odložené objekty jsou jen kontejnery pro budoucí hodnotu nejsou hodnotou samotnou. Předpokládejme například, že jste měli následující:

function checkPassword() {
    return $.ajax({
        url: '/password',
        data: {
            username: $('#username').val(),
            password: $('#password').val()
        },
        type: 'POST',
        dataType: 'json'
    });
}

if (checkPassword()) {
    // Tell the user they're logged in
}

Tento kód nepochopí výše uvedené problémy s asynchronií. Konkrétně $.ajax() nezablokuje kód, když kontroluje stránku '/ password' na vašem serveru - pošle požadavek serveru a zatímco čeká, okamžitě vrátí objekt jQuery Ajax Deferred, nikoli odpověď ze serveru. To znamená, že příkaz if bude vždy dostávat tento odložený objekt, bude jej považovat za true a bude postupovat, jako by byl uživatel přihlášen.

Oprava je však jednoduchá:

checkPassword()
.done(function(r) {
    if (r) {
        // Tell the user they're logged in
    } else {
        // Tell the user their password was bad
    }
})
.fail(function(x) {
    // Tell the user something bad happened
});

Nedoporučuje se: Synchronní volání "Ajax"

Jak jsem zmínil, některé (!) Asynchronní operace mají synchronní protějšky. Nepodporuji jejich použití, ale pro úplnost je zde způsob, jakým byste provedli synchronní volání:

Bez jQuery

Pokud přímo použijete XMLHTTPRequest object, předejte false jako třetí argument do .open .

jQuery

Pokud použijete jQuery , můžete nastavit async možnost false. Všimněte si, že tato volba je zastaralé od jQuery 1.8. Potom můžete ještě použít success zpětné volání nebo přistupovat k vlastnosti responseText objektu jqXHR :

function foo() {
    var jqXHR = $.ajax({
        //...
        async: false
    });
    return jqXHR.responseText;
}

Pokud používáte jinou metodu jQuery Ajax, jako je $.get, $.getJSON atd., Musíte ji změnit na $.ajax (protože parametr $.ajax můžete předávat pouze konfigurační parametry).

Hlavy nahoru! Není možné provést synchronní JSONP request. JSONP je svým charakterem vždy asynchronní (ještě jeden důvod, proč ani tuto možnost neuvažovat).

5203
Felix Kling

Pokud jste ne používáte jQuery ve svém kódu, tato odpověď je pro vás

Váš kód by měl být něco jako toto:

function foo() {
    var httpRequest = new XMLHttpRequest();
    httpRequest.open('GET', "/echo/json");
    httpRequest.send();
    return httpRequest.responseText;
}

var result = foo(); // always ends up being 'undefined'

Felix Kling udělal skvělou práci, když napsal odpověď pro lidi používající jQuery pro AJAX, rozhodl jsem se poskytnout alternativu pro lidi, kteří nejsou.

( Poznámka: pro ty, kteří používají nové fetch API, Angular nebo sliby jsem přidal další odpověď níže )


Čemu čelíte

Toto je krátké shrnutí "Vysvětlení problému" z druhé odpovědi, pokud si nejste jisti po přečtení tohoto článku, přečtěte si to.

Ain AJAX znamená asynchronní . To znamená, že odeslání požadavku (nebo spíše přijetí odpovědi) je vyřazeno z běžného prováděcího toku. Ve vašem příkladu .send vrací okamžitě a další příkaz, return result;, je proveden před tím, než byla funkce, kterou jste předali jako zpětné volání success, vyvolána.

To znamená, že když se vrátíte, definovaný posluchač se ještě nespustil, což znamená, že hodnota, kterou vracíte, nebyla definována.

Zde je jednoduchá analogie

function getFive(){ 
    var a;
    setTimeout(function(){
         a=5;
    },10);
    return a;
}

(Fiddle)

Hodnota a je vrácena undefined, protože část a=5 ještě nebyla provedena. AJAX se chová takto, vracíte hodnotu dříve, než server dostane šanci vašemu prohlížeči říct, jaká hodnota je.

Jedním z možných řešení tohoto problému je kód re-active, sdělením programu, co má dělat, když je výpočet dokončen.

function onComplete(a){ // When the code completes, do this
    alert(a);
}

function getFive(whenDone){ 
    var a;
    setTimeout(function(){
         a=5;
         whenDone(a);
    },10);
}

Toto se nazývá CPS . V podstatě předáváme getFive akci, která se má provést, když je dokončena, říkáme našim kódům, jak reagovat, když je událost dokončena (jako naše volání AJAX nebo v tomto případě časový limit).

Použití by bylo:

getFive(onComplete);

Který by měl na obrazovce upozornit "5". (Fiddle) .

Možné řešení

V podstatě existují dva způsoby, jak to vyřešit:

  1. Proveďte volání AJAX synchrónně (umožňuje volání SJAX).
  2. Restrukturalizace kódu pracovat správně s zpětná volání.

1. Synchronní AJAX - Nedělejte to!

Pokud jde o synchronní AJAX, nedělejte to! Felixova odpověď vyvolává některé přesvědčivé argumenty o tom, proč je to špatný nápad. Chcete-li to shrnout, bude zmrazení prohlížeče uživatele, dokud server vrátí odpověď a vytvořit velmi špatné uživatelské zkušenosti. Zde je další stručné shrnutí z MDN o tom, proč:

XMLHttpRequest podporuje synchronní i asynchronní komunikaci. Obecně by však měly být upřednostňovány asynchronní požadavky před synchronními požadavky z důvodů výkonu.

Stručně řečeno, synchronní požadavky blokují provádění kódu ... ... což může způsobit vážné problémy ...

Pokud to uděláte mít, můžete předat příznak: Zde je návod:

var request = new XMLHttpRequest();
request.open('GET', 'yourURL', false);  // `false` makes the request synchronous
request.send(null);

if (request.status === 200) {// That's HTTP for 'ok'
  console.log(request.responseText);
}

2. Kód restrukturalizace

Nechte svou funkci přijmout zpětné volání. V příkladu může být kód foo přijat, aby přijal zpětné volání. Náš kód vám řekne, jak reagovat když foo dokončí.

Tak:

var result = foo();
// code that depends on `result` goes here

Stává se:

foo(function(result) {
    // code that depends on `result`
});

Zde jsme prošli anonymní funkcí, ale mohli bychom stejně snadno předat odkaz na existující funkci, takže to vypadá takto:

function myHandler(result) {
    // code that depends on `result`
}
foo(myHandler);

Pro více informací o tom, jak je tento způsob zpětného volání proveden, zkontrolujte Felixovu odpověď.

Nyní definujme foo, aby podle toho jednal

function foo(callback) {
    var httpRequest = new XMLHttpRequest();
    httpRequest.onload = function(){ // when the request is loaded
       callback(httpRequest.responseText);// we're calling our method
    };
    httpRequest.open('GET', "/echo/json");
    httpRequest.send();
}

(fiddle)

Nyní jsme provedli naši funkci foo, která přijímá akci, která se spustí, když AJAX úspěšně dokončí, můžeme ji dále rozšířit kontrolou, zda stav odezvy není 200 a podle toho postupovat (vytvořit handler a podobně). Efektivní řešení našeho problému.

Pokud se vám stále nedaří pochopit tento přečtěte si návod [AJAX začínání průvodce na MDN.

1002

XMLHttpRequest 2 (nejprve přečtěte si odpovědi z Benjamin Gruenbaum & Felix Kling )

Pokud nepoužíváte jQuery a chcete pěkný krátký XMLHttpRequest 2, který funguje na moderních prohlížečích a také v mobilních prohlížečích, doporučuji použít tento způsob:

function ajax(a, b, c){ // URL, callback, just a placeholder
  c = new XMLHttpRequest;
  c.open('GET', a);
  c.onload = b;
  c.send()
}

Jak můžete vidět:

  1. Je kratší než všechny ostatní funkce.
  2. Zpětné volání je nastaveno přímo (takže žádné další zbytečné uzavření).
  3. Používá nové onload (takže nemusíte kontrolovat stav připravenosti &&)
  4. Tam jsou některé jiné situace, které si nepamatuji, že XMLHttpRequest 1 nepříjemné.

Odpověď tohoto volání Ajax lze získat dvěma způsoby (tři pomocí jména XMLHttpRequest var):

Nejjednodušší:

this.response

Nebo pokud z nějakého důvodu bind() zavoláte zpět do třídy:

e.target.response

Příklad:

function callback(e){
  console.log(this.response);
}
ajax('URL', callback);

Nebo (výše uvedené je lepší anonymní funkce jsou vždy problém):

ajax('URL', function(e){console.log(this.response)});

Nic jednoduššího.

Někteří lidé budou pravděpodobně říkat, že je lepší použít onreadystatechange nebo dokonce proměnný název XMLHttpRequest. To je špatně.

Vyzkoušet Rozšířené funkce XMLHttpRequest

Podporoval všechny * moderní prohlížeče. A mohu potvrdit, že používám tento přístup, protože XMLHttpRequest 2 existuje. Nikdy jsem neměl žádný problém ve všech prohlížečích, které používám.

onreadystatechange je užitečné pouze v případě, že chcete získat záhlaví ve stavu 2.

Použití proměnné XMLHttpRequest je další velkou chybou, protože je třeba provést zpětné volání uvnitř uzávěrů onload/oreadystatechange, jinak jste je ztratili.


Nyní, pokud chcete něco složitějšího pomocí Post a FormData, můžete tuto funkci snadno rozšířit:

function x(a, b, e, d, c){ // URL, callback, method, formdata or {key:val},placeholder
  c = new XMLHttpRequest;
  c.open(e||'get', a);
  c.onload = b;
  c.send(d||null)
}

Opět ... je to velmi krátká funkce, ale dělá to.

Příklady použití:

x(url, callback); // By default it's get so no need to set
x(url, callback, 'post', {'key': 'val'}); // No need to set post data

Nebo předejte celý prvek formuláře (document.getElementsByTagName('form')[0]):

var fd = new FormData(form);
x(url, callback, 'post', fd);

Nebo můžete nastavit některé vlastní hodnoty:

var fd = new FormData();
fd.append('key', 'val')
x(url, callback, 'post', fd);

Jak vidíte, neimplementoval jsem synchronizaci ... je to špatná věc.

Řekl jsem, že ... proč to neudělat snadněji?


Jak je uvedeno v komentáři, použití chyby && synchrónně zcela porušuje bod odpovědi. Což je pěkný krátký způsob, jak použít Ajax správným způsobem?

Chyba obsluha

function x(a, b, e, d, c){ // URL, callback, method, formdata or {key:val}, placeholder
  c = new XMLHttpRequest;
  c.open(e||'get', a);
  c.onload = b;
  c.onerror = error;
  c.send(d||null)
}

function error(e){
  console.log('--Error--', this.type);
  console.log('this: ', this);
  console.log('Event: ', e)
}
function displayAjax(e){
  console.log(e, this);
}
x('WRONGURL', displayAjax);

Ve výše uvedeném skriptu máte obslužnou rutinu chyb, která je staticky definována, takže neohrožuje funkci. Popisovač chyb lze použít i pro jiné funkce.

Ale aby se opravdu dostalo chyby, je only way způsob, jak napsat špatnou adresu URL, v tomto případě každý prohlížeč vyvolá chybu.

Popisovače chyb jsou užitečné, pokud nastavíte vlastní záhlaví, nastavíte typ bufferu na buffer blob pole nebo cokoliv ...

Dokonce i když předáte 'POSTAPAPAP' jako metodu, nebude to chyba.

Dokonce i když předáte 'fdggdgilfdghfldj' jako formdata, nebude to chyba.

V prvním případě je chyba uvnitř displayAjax() pod this.statusText jako Method not Allowed.

Ve druhém případě to prostě funguje. Pokud jste předali správná data, musíte zkontrolovat na straně serveru.

cross-domain není povoleno automaticky.

V chybové reakci nejsou žádné chybové kódy.

Existuje pouze this.type, které je nastaveno na chybu.

Proč přidávat obslužný program chyb, pokud nemáte naprosto žádnou kontrolu nad chybami? Většina chyb se vrací zpět do funkce zpětného volání displayAjax().

Takže: Není třeba provádět kontroly chyb, pokud jste schopni kopírovat a vložit adresu URL správně. ;)

PS: Jako první test jsem napsal x ('x', displayAjax) ..., a to je úplně odpověď ... ??? Tak jsem zkontroloval složku, kde se nachází HTML, a tam byl soubor nazvaný 'x.xml'. Takže i když zapomenete rozšíření souboru XMLHttpRequest 2 WILL FIND IT. Já jsem LOL'd


Číst soubor synchronní

Nedělej to.

Pokud chcete prohlížeč na chvíli zablokovat, nahrajte soubor Nice big .txt synchronní.

function omg(a, c){ // URL
  c = new XMLHttpRequest;
  c.open('GET', a, true);
  c.send();
  return c; // Or c.response
}

Teď můžete udělat

 var res = omg('thisIsGonnaBlockThePage.txt');

Neexistuje žádný jiný způsob, jak to udělat neanynchronním způsobem. (Jo, s setTimeout smyčkou ... ale vážně?)

Dalším bodem je ... pokud pracujete s rozhraním API nebo jen s vlastními soubory v seznamu nebo s tím, co pro každou žádost používáte vždy různé funkce ...

Pouze v případě, že máte stránku, na kterou se načte vždy stejný XML/JSON nebo co potřebujete pouze jedna funkce. V takovém případě trochu upravte funkci Ajax a nahraďte b svou speciální funkcí.


Výše uvedené funkce jsou určeny pro základní použití.

Chcete-li funkci EXTEND ...

Ano můžeš.

Používám spoustu API a jedna z prvních funkcí, které jsem integroval do každé HTML stránky, je první funkcí Ajax v této odpovědi, pouze s GET ...

Ale s XMLHttpRequest 2 můžete udělat spoustu věcí:

Udělal jsem download manager (s použitím rozsahů na obou stranách s životopisem, filereaderem, souborovým systémem), různými konvertory obrazových konvertorů pomocí plátna, naplňováním databází web SQL pomocí base64images a mnohem více ... Ale v těchto případech byste měli vytvořit funkci pouze pro to účel ... někdy potřebujete blob, pole buffery, můžete nastavit záhlaví, přepsat mimetype a tam je mnohem více ...

Ale otázkou je, jak vrátit odpověď Ajax ... (Přidal jsem snadný způsob.)

363
cocco

Pokud používáte sliby, tato odpověď je pro vás.

To znamená AngularJS, jQuery (s odloženým), nativní nahrazení XHR (fetch), EmberJS, uložení BackboneJS nebo libovolnou knihovnu uzlů, která vrací sliby.

Váš kód by měl být něco jako toto:

function foo() {
    var data;
    // or $.get(...).then, or request(...).then, or query(...).then
    fetch("/echo/json").then(function(response){
        data = response.json();
    });
    return data;
}

var result = foo(); // result is always undefined no matter what.

Felix Kling odvedl skvělou práci, když napsal odpověď pro lidi používající jQuery s zpětnými voláními pro AJAX. Mám odpověď na nativní XHR. Tato odpověď je pro obecné použití slibů buď na frontendu nebo backendu.


Hlavní problém

Model souběžnosti JavaScriptu v prohlížeči a na serveru s NodeJS/io.js je asynchronní a reaktivní.

Kdykoliv zavoláte metodu, která vrací slib, obslužné rutiny then jsou vždy provedeny asynchronně - tj. po kódu pod nimi, který není v .then handleru.

To znamená, že když vracíte data popisovač then, který jste definovali, se ještě nespustil. To znamená, že hodnota, kterou vracíte, nebyla nastavena na správnou hodnotu v čase.

Zde je jednoduchá analogie problému:

    function getFive(){
        var data;
        setTimeout(function(){ // set a timer for one second in the future
           data = 5; // after a second, do this
        }, 1000);
        return data;
    }
    document.body.innerHTML = getFive(); // `undefined` here and not 5

Hodnota data je undefined, protože část data = 5 ještě nebyla provedena. Pravděpodobně se provede za sekundu, ale do té doby je pro vrácenou hodnotu irelevantní.

Vzhledem k tomu, že se operace ještě nestala (AJAX, serverové volání, IO, časovač), vracíte hodnotu před tím, než žádost dostala šanci sdělit vašemu kódu, jaká hodnota je.

Jedním z možných řešení tohoto problému je kód re-active, sdělením programu, co má dělat, když je výpočet dokončen. Sliby to aktivně umožňují časovou (časově citlivou) povahou.

Rychlá rekapitulace slibů

Slib je hodnota v čase. Sliby mají stav, začínají jako nevyřízené a nemají žádnou hodnotu a mohou se vyrovnat:

  • splněno což znamená, že výpočet proběhl úspěšně.
  • odmítnuto což znamená, že výpočet selhal.

Slib může změnit pouze stavy jednou, po kterém bude navždy zůstat ve stejném stavu. then handlery můžete připojit k slibům extrahovat jejich hodnotu a zpracovávat chyby. then handlers umožňují zřetězení hovorů. Sliby jsou vytvořeny pomocí pomocí API, které je vrátí . Například, více moderní AJAX nahrazení fetch nebo jQuery je $.get návratové sliby.

Když zavoláme .then na slib a return něco z toho - dostaneme slib pro zpracovaná hodnota. Pokud vrátíme další slib, dostaneme úžasné věci, ale pojďme držet koně.

Se sliby

Podívejme se, jak můžeme výše uvedenou otázku vyřešit sliby. Nejprve si ukážeme naše pochopení slibných stavů shora pomocí konstruktoru Promise pro vytvoření funkce zpoždění:

function delay(ms){ // takes amount of milliseconds
    // returns a new promise
    return new Promise(function(resolve, reject){
        setTimeout(function(){ // when the time is up
            resolve(); // change the promise to the fulfilled state
        }, ms);
    });
}

Poté, co jsme převedli setTimeout na použití slibů, můžeme použít then, aby bylo možné počítat:

function delay(ms){ // takes amount of milliseconds
  // returns a new promise
  return new Promise(function(resolve, reject){
    setTimeout(function(){ // when the time is up
      resolve(); // change the promise to the fulfilled state
    }, ms);
  });
}

function getFive(){
  // we're RETURNING the promise, remember, a promise is a wrapper over our value
  return delay(100).then(function(){ // when the promise is ready
      return 5; // return the value 5, promises are all about return values
  })
}
// we _have_ to wrap it like this in the call site, we can't access the plain value
getFive().then(function(five){ 
   document.body.innerHTML = five;
});

Namísto vrácení hodnota, které nemůžeme udělat kvůli modelu souběžnosti, v podstatě vracíme hodnotu wrapper za hodnotu, kterou můžeme rozbalit s then. Je to jako krabice, kterou můžete otevřít pomocí then.

Použití tohoto

To platí pro původní volání API, můžete:

function foo() {
    // RETURN the promise
    return fetch("/echo/json").then(function(response){
        return response.json(); // process it inside the `then`
    });
}

foo().then(function(response){
    // access the value inside the `then`
})

Tak to funguje stejně dobře. Dozvěděli jsme se, že nemůžeme vrátit hodnoty z již asynchronních volání, ale můžeme použít sliby a řetězit je k provedení zpracování. Nyní víme, jak vrátit odpověď z asynchronního volání.

ES2015 (ES6)

ES6 zavádí generátory které jsou funkce, které se mohou vrátit do středu a pak obnovit bod, ve kterém byly. To je obvykle užitečné pro sekvence, například:

function* foo(){ // notice the star, this is ES6 so new browsers/node/io only
    yield 1;
    yield 2;
    while(true) yield 3;
}

Je funkce, která vrací iterátor přes posloupnost 1,2,3,3,3,3,...., kterou lze iterovat. I když je to zajímavé samo o sobě a otevírá prostor pro mnoho možností, je zde jeden zajímavý případ.

Je-li sekvence, kterou produkujeme, posloupností akcí, nikoli čísel, můžeme tuto funkci kdykoliv pozastavit, kdykoli je akce vydána, a počkejte na ni, než funkci obnovíme. Takže místo posloupnosti čísel potřebujeme posloupnost budoucí hodnot - to je: slibuje.

Tento poněkud choulostivý, ale velmi silný trik nám umožňuje psát asynchronní kód synchronním způsobem. Existuje několik "běžců", kteří to dělají za vás, psaní je krátké řádky kódu, ale je nad rámec této odpovědi. Použiju zde Promise.coroutine od Bluebirdu, ale existují i ​​další balíčky jako co nebo Q.async.

var foo = coroutine(function*(){
    var data = yield fetch("/echo/json"); // notice the yield
    // code here only executes _after_ the request is done
    return data.json(); // data is defined
});

Tato metoda vrací samotný slib, který můžeme konzumovat z jiných coroutines. Například:

var main = coroutine(function*(){
   var bar = yield foo(); // wait our earlier coroutine, it returns a promise
   // server call done here, code below executes when done
   var baz = yield fetch("/api/users/"+bar.userid); // depends on foo's result
   console.log(baz); // runs after both requests done
});
main();

ES2016 (ES7)

V ES7 je to dále standardizováno, existuje několik návrhů právě teď, ale ve všech z nich můžete slibovat await. Toto je jen "cukr" (hezčí syntaxe) pro návrh ES6 výše přidáním klíčových slov async a await. Výše uvedený příklad:

async function foo(){
    var data = await fetch("/echo/json"); // notice the await
    // code here only executes _after_ the request is done
    return data.json(); // data is defined
}

To stále vrací slib jen to samé :)

292

Používáte Ajax nesprávně. Cílem není vrátit nic, ale místo toho předat data něčemu, co se nazývá funkce zpětného volání, která data zpracovává.

To je:

function handleData( responseData ) {

    // Do what you want with the data
    console.log(responseData);
}

$.ajax({
    url: "hi.php",
    ...
    success: function ( data, status, XHR ) {
        handleData(data);
    }
});

Vrácení čehokoliv v odesílacím obslužném programu nebude nic dělat. Namísto toho musíte data buď vypustit, nebo to, co chcete, provést přímo v rámci funkce úspěchu.

230
Nic

Nejjednodušším řešením je vytvořit funkci JavaScriptu a zavolat ji pro zpětné volání Ajax success.

function callServerAsync(){
    $.ajax({
        url: '...',
        success: function(response) {

            successCallback(response);
        }
    });
}

function successCallback(responseObj){
    // Do something like read the response and show data
    alert(JSON.stringify(responseObj)); // Only applicable to JSON response
}

function foo(callback) {

    $.ajax({
        url: '...',
        success: function(response) {
           return callback(null, response);
        }
    });
}

var result = foo(function(err, result){
          if (!err)
           console.log(result);    
}); 
219
Hemant Bavle

Odpovím s hrůzou vypadajícím, ručně kresleným komikem. Druhý obrázek je důvod, proč result je undefined v příkladu kódu.

 enter image description here

195

Angular1

Pro lidi, kteří používají AngularJS , zvládne tuto situaci pomocí Promises.

Zde říká,

Sliby mohou být použity k odemknutí asynchronních funkcí a umožňují jednořetězcové více funkcí dohromady.

Můžete najít pěkné vysvětlení zde také.

Příklad naleznete v dokumentech níže.

  promiseB = promiseA.then(
    function onSuccess(result) {
      return result + 1;
    }
    ,function onError(err) {
      //Handle error
    }
  );

 // promiseB will be resolved immediately after promiseA is resolved 
 // and its value will be the result of promiseA incremented by 1.

Angular2 a později

V Angular2 se podívejte na následující příklad, ale jeho doporučeno použít Observables s Angular2.

 search(term: string) {
     return this.http
  .get(`https://api.spotify.com/v1/search?q=${term}&type=artist`)
  .map((response) => response.json())
  .toPromise();

}

Tímto způsobem můžete konzumovat,

search() {
    this.searchService.search(this.searchField.value)
      .then((result) => {
    this.result = result.artists.items;
  })
  .catch((error) => console.error(error));
}

Viz originál post zde. Ale TypeScript nepodporuje nativní es6 Promises , pokud ho chcete použít, možná budete potřebovat plugin pro to.

Navíc zde jsou sliby spec define.

143

Většina odpovědí zde poskytuje užitečné návrhy, když máte jednu asynchronní operaci, ale někdy to nastane, když potřebujete provést asynchronní operaci pro každý záznam v poli nebo jiné struktuře podobné seznamu. Je to pokušení:

// WRONG
var results = [];
theArray.forEach(function(entry) {
    doSomethingAsync(entry, function(result) {
        results.Push(result);
    });
});
console.log(results); // E.g., using them, returning them, etc.

Příklad:

// WRONG
var theArray = [1, 2, 3];
var results = [];
theArray.forEach(function(entry) {
    doSomethingAsync(entry, function(result) {
        results.Push(result);
    });
});
console.log("Results:", results); // E.g., using them, returning them, etc.

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

Důvodem, proč to nefunguje, je to, že zpětná volání z doSomethingAsync ještě nebyly spuštěny v době, kdy se snažíte výsledky využít.

Takže pokud máte pole (nebo seznam nějakého druhu) a chcete provádět asynchronní operace pro každou položku, máte dvě možnosti: Proveďte operace paralelně (překrývající se) nebo v sériích (postupně po sobě).

Paralelní

Můžete spustit všechny z nich a sledovat, kolik zpětných volání očekáváte, a poté použít výsledky, když jste získali mnoho zpětných volání:

var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
    doSomethingAsync(entry, function(result) {
        results[index] = result;
        if (--expecting === 0) {
            // Done!
            console.log("Results:", results); // E.g., using the results
        }
    });
});

Příklad:

var theArray = [1, 2, 3];
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
    doSomethingAsync(entry, function(result) {
        results[index] = result;
        if (--expecting === 0) {
            // Done!
            console.log("Results:", results); // E.g., using the results
        }
    });
});

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

(Mohli bychom se zbavit expecting a použít results.length === theArray.length, ale ponecháváme otevřenou možnost, že theArray se změní, když jsou hovory nevyřízené ...)

Všimněte si, že používáme index z forEach k uložení výsledku do results ve stejné pozici jako položka, ke které se vztahuje, a to i v případě, že výsledky dorazí mimo pořadí (protože asynchronní volání nemusí být nutně dokončena v pořadí, v jakém byla spuštěna) ).

Ale co když potřebujete návrat ty výsledky z funkce? Jak ukázali ostatní odpovědi, nemůžete; musíte mít svou funkci přijmout a zavolat zpětné volání (nebo vrátit Promise ). Zde je verze zpětného volání:

function doSomethingWith(theArray, callback) {
    var results = [];
    var expecting = theArray.length;
    theArray.forEach(function(entry, index) {
        doSomethingAsync(entry, function(result) {
            results[index] = result;
            if (--expecting === 0) {
                // Done!
                callback(results);
            }
        });
    });
}
doSomethingWith(theArray, function(results) {
    console.log("Results:", results);
});

Příklad:

function doSomethingWith(theArray, callback) {
    var results = [];
    var expecting = theArray.length;
    theArray.forEach(function(entry, index) {
        doSomethingAsync(entry, function(result) {
            results[index] = result;
            if (--expecting === 0) {
                // Done!
                callback(results);
            }
        });
    });
}
doSomethingWith([1, 2, 3], function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

Nebo zde je verze, která vrací namísto Promise:

function doSomethingWith(theArray) {
    return new Promise(function(resolve) {
        var results = [];
        var expecting = theArray.length;
        theArray.forEach(function(entry, index) {
            doSomethingAsync(entry, function(result) {
                results[index] = result;
                if (--expecting === 0) {
                    // Done!
                    resolve(results);
                }
            });
        });
    });
}
doSomethingWith(theArray).then(function(results) {
    console.log("Results:", results);
});

Samozřejmě, pokud doSomethingAsync nám prošly chyby, použijeme reject k odmítnutí slibu, když se vyskytne chyba.)

Příklad:

function doSomethingWith(theArray) {
    return new Promise(function(resolve) {
        var results = [];
        var expecting = theArray.length;
        theArray.forEach(function(entry, index) {
            doSomethingAsync(entry, function(result) {
                results[index] = result;
                if (--expecting === 0) {
                    // Done!
                    resolve(results);
                }
            });
        });
    });
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

(Nebo můžete střídavě vytvořit obálku pro doSomethingAsync, která vrací slib a poté proveďte níže uvedený postup ...)

Pokud doSomethingAsync vám dává Promise , můžete použít Promise.all :

function doSomethingWith(theArray) {
    return Promise.all(theArray.map(function(entry) {
        return doSomethingAsync(entry);
    }));
}
doSomethingWith(theArray).then(function(results) {
    console.log("Results:", results);
});

Pokud víte, že doSomethingAsync bude ignorovat druhý a třetí argument, můžete ho předat přímo do map (map zavolá zpětné volání se třemi argumenty, ale většina lidí používá první většinu času):

function doSomethingWith(theArray) {
    return Promise.all(theArray.map(doSomethingAsync));
}
doSomethingWith(theArray).then(function(results) {
    console.log("Results:", results);
});

Příklad:

function doSomethingWith(theArray) {
    return Promise.all(theArray.map(doSomethingAsync));
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value) {
    console.log("Starting async operation for " + value);
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("Completing async operation for " + value);
            resolve(value * 2);
        }, Math.floor(Math.random() * 200));
    });
}
.as-console-wrapper {
  max-height: 100% !important;
}

Všimněte si, že Promise.all řeší svůj slib s řadou výsledků všech slibů, které mu dáte, když jsou všechny vyřešeny, nebo odmítá svůj slib, když první slibů, které mu sdělíte, odmítne.

Série

Předpokládejme, že nechcete, aby operace byly paralelní? Chcete-li je spouštět jeden po druhém, musíte počkat na dokončení každé operace, než začnete další. Zde je příklad funkce, která tuto funkci provádí, a zavolá zpětné volání s výsledkem:

function doSomethingWith(theArray, callback) {
    var results = [];
    doOne(0);
    function doOne(index) {
        if (index < theArray.length) {
            doSomethingAsync(theArray[index], function(result) {
                results.Push(result);
                doOne(index + 1);
            });
        } else {
            // Done!
            callback(results);
        }
    }
}
doSomethingWith(theArray, function(results) {
    console.log("Results:", results);
});

(Protože děláme práci v seriálu, můžeme použít results.Push(result), protože víme, že výsledky nedostaneme mimo řádek. Ve výše uvedeném jsme mohli použít results[index] = result;, ale v některých z následujících příkladů jsme don ' t mít index k použití.)

Příklad:

function doSomethingWith(theArray, callback) {
    var results = [];
    doOne(0);
    function doOne(index) {
        if (index < theArray.length) {
            doSomethingAsync(theArray[index], function(result) {
                results.Push(result);
                doOne(index + 1);
            });
        } else {
            // Done!
            callback(results);
        }
    }
}
doSomethingWith([1, 2, 3], function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

(Nebo opět vytvořte obal pro doSomethingAsync, který vám slibuje a udělejte níže ...)

Pokud doSomethingAsync vám dá slib, pokud můžete použít syntaxi ES2017 + (snad s transpilerem jako Babel ), můžete použít funkci async with for-of a await :

async function doSomethingWith(theArray) {
    const results = [];
    for (const entry of theArray) {
        results.Push(await doSomethingAsync(entry));
    }
    return results;
}
doSomethingWith(theArray).then(results => {
    console.log("Results:", results);
});

Příklad:

async function doSomethingWith(theArray) {
    const results = [];
    for (const entry of theArray) {
        results.Push(await doSomethingAsync(entry));
    }
    return results;
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value) {
    console.log("Starting async operation for " + value);
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("Completing async operation for " + value);
            resolve(value * 2);
        }, Math.floor(Math.random() * 200));
    });
}
.as-console-wrapper {
  max-height: 100% !important;
}

Pokud nemůžete použít syntaxi ES2017 + (zatím), můžete použít variace na vzor - "Slib snížit" (to je složitější než obvyklé snížení slibů, protože výsledek nepřenášíme z jednoho do druhého). další, ale namísto toho shromažďování jejich výsledků v poli):

function doSomethingWith(theArray) {
    return theArray.reduce(function(p, entry) {
        return p.then(function(results) {
            return doSomethingAsync(entry).then(function(result) {
                results.Push(result);
                return results;
            });
        });
    }, Promise.resolve([]));
}
doSomethingWith(theArray).then(function(results) {
    console.log("Results:", results);
});

Příklad:

function doSomethingWith(theArray) {
    return theArray.reduce(function(p, entry) {
        return p.then(function(results) {
            return doSomethingAsync(entry).then(function(result) {
                results.Push(result);
                return results;
            });
        });
    }, Promise.resolve([]));
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value) {
    console.log("Starting async operation for " + value);
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("Completing async operation for " + value);
            resolve(value * 2);
        }, Math.floor(Math.random() * 200));
    });
}
.as-console-wrapper {
  max-height: 100% !important;
}

... což je méně těžkopádné s ES2015 + šipkami :

function doSomethingWith(theArray) {
    return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => {
        results.Push(result);
        return results;
    })), Promise.resolve([]));
}
doSomethingWith(theArray).then(results => {
    console.log("Results:", results);
});

Příklad:

function doSomethingWith(theArray) {
    return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => {
        results.Push(result);
        return results;
    })), Promise.resolve([]));
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value) {
    console.log("Starting async operation for " + value);
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("Completing async operation for " + value);
            resolve(value * 2);
        }, Math.floor(Math.random() * 200));
    });
}
.as-console-wrapper {
  max-height: 100% !important;
}
122
T.J. Crowder

Podívejte se na tento příklad:

var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope,$http) {

    var getJoke = function(){
        return $http.get('http://api.icndb.com/jokes/random').then(function(res){
            return res.data.value;  
        });
    }

    getJoke().then(function(res) {
        console.log(res.joke);
    });
});

Jak vidíte, getJoke je vrací a resolved slib (je vyřešen při návratu res.data.value). Takže počkejte, dokud nebude dokončeno $ http.get request a poté console.log (res.joke) (jako normální asynchronní tok).

Toto je plnkr:

http://embed.plnkr.co/XlNR7HpCaIhJxskMJfSg/

Způsob ES6 (async - čeká)

(function(){
  async function getJoke(){
    let response = await fetch('http://api.icndb.com/jokes/random');
    let data = await response.json();
    return data.value;
  }

  getJoke().then((joke) => {
    console.log(joke);
  });
})();
97

Další přístup k vrácení hodnoty z asynchronní funkce je předat objekt, který uloží výsledek z asynchronní funkce.

Zde je příklad:

var async = require("async");

// This wires up result back to the caller
var result = {};
var asyncTasks = [];
asyncTasks.Push(function(_callback){
    // some asynchronous operation
    $.ajax({
        url: '...',
        success: function(response) {
            result.response = response;
            _callback();
        }
    });
});

async.parallel(asyncTasks, function(){
    // result is available after performing asynchronous operation
    console.log(result)
    console.log('Done');
});

Používám objekt result k uložení hodnoty během asynchronní operace. To umožňuje, aby byl výsledek dostupný i po asynchronní úloze.

Tento přístup používám hodně. Zajímalo by mě, jak dobře tento přístup funguje tam, kde je zapojeno zapojení výsledku zpět do modulů.

85
jsbisht

Toto je jedno z míst, které dvě cesty datové vazby to je použito v mnoha nových frameworkech JavaScriptu, které budou pro vás fungovat ...

Pokud tedy používáte Angular, React nebo jiné rámce, které dělají dva způsoby vazby dat, tento problém je pro vás jednoduše stanoven, takže ve snadném Wordu je váš výsledek undefined na adrese první fáze, takže jste dostali result = undefined dříve, než obdržíte data, pak jakmile se dostanete výsledek, bude aktualizován a dostat přiřazeny na novou hodnotu, která odpovídá na vaše volání Ajax ...

Ale jak to můžete udělat v čistém javascriptu nebo jQuery například, jak jste se ptali v této otázce?

Můžete použít zpětné volání , slib a nedávno pozorovatelný pro jeho zpracování, například v slibech máme nějakou funkci jako úspěch () nebo pak () který bude proveden, když jsou vaše data připravena pro vás, stejně jako callback nebo subscribe function on pozorovatelný .

Například ve vašem případě, který používáte jQuery , můžete něco takového udělat:

$(document).ready(function(){
    function foo() {
        $.ajax({url: "api/data", success: function(data){
            fooDone(data); //after we have data, we pass it to fooDone
        }});
    };

    function fooDone(data) {
        console.log(data); //fooDone has the data and console.log it
    };

    foo(); //call happens here
});

Pro více informací studujte o slibech a pozorovatelných což jsou novější způsoby, jak to provést asynchronně.

80
Alireza

I když sliby a zpětná volání fungují v mnoha situacích dobře, je to bolest vzadu, aby vyjádřila něco jako:

if (!name) {
  name = async1();
}
async2(name);

Měli byste skončit přes async1; zkontrolujte, zda je name nedefinováno nebo ne, a volejte odpovídajícím způsobem zpětné volání.

async1(name, callback) {
  if (name)
    callback(name)
  else {
    doSomething(callback)
  }
}

async1(name, async2)

I když je okay v malých příkladech je to nepříjemné, když máte spoustu podobných případů a zpracování chyb.

Fibers pomáhá při řešení problému.

var Fiber = require('fibers')

function async1(container) {
  var current = Fiber.current
  var result
  doSomething(function(name) {
    result = name
    fiber.run()
  })
  Fiber.yield()
  return result
}

Fiber(function() {
  var name
  if (!name) {
    name = async1()
  }
  async2(name)
  // Make any number of async calls from here
}

Projekt můžete zkontrolovat zde .

77
rohithpr

Krátká odpověď je, musíte provést zpětné volání takto:

function callback(response) {
    // Here you can do what ever you want with the response object.
    console.log(response);
}

$.ajax({
    url: "...",
    success: callback
});
73

Následující příklad jsem napsal ukazuje, jak

  • Zpracování asynchronních volání HTTP;
  • Počkejte na odpověď z každého volání API;
  • Použít vzor Promise ;
  • Použijte vzor Promise.all pro spojení více HTTP volání;

Tento pracovní příklad je samostatný. Definuje jednoduchý objekt požadavku, který používá objekt XMLHttpRequest okna k volání. Bude definovat jednoduchou funkci, která bude čekat na dokončení slibů.

Kontext. Příkladem je dotazování koncového bodu Spotify Web API s cílem vyhledat objekty playlist pro danou sadu řetězců dotazů:

[
 "search?type=playlist&q=%22Doom%20metal%22",
 "search?type=playlist&q=Adele"
]

Pro každou položku nový slib zapálí blok - ExecutionBlock, analyzuje výsledek, naplánuje novou sadu slibů založenou na poli výsledků, což je seznam objektů Spotify user a provede nové volání HTTP v rámci ExecutionProfileBlock asynchronně.

Potom můžete zobrazit vnořenou strukturu Promise, která vám umožní plodit více a zcela asynchronních vnořených volání HTTP a spojit výsledky z každé podmnožiny volání prostřednictvím Promise.all.

NOTENedávné Spotify search API budou vyžadovat token přístupu, který bude specifikován v hlavičkách požadavku:

-H "Authorization: Bearer {your access token}" 

Chcete-li tedy spustit následující příklad, který chcete v hlavičkách požadavku umístit token přístupu:

var spotifyAccessToken = "YourSpotifyAccessToken";
var console = {
    log: function(s) {
        document.getElementById("console").innerHTML += s + "<br/>"
    }
}

// Simple XMLHttpRequest
// based on https://davidwalsh.name/xmlhttprequest
SimpleRequest = {
    call: function(what, response) {
        var request;
        if (window.XMLHttpRequest) { // Mozilla, Safari, ...
            request = new XMLHttpRequest();
        } else if (window.ActiveXObject) { // Internet Explorer
            try {
                request = new ActiveXObject('Msxml2.XMLHTTP');
            }
            catch (e) {
                try {
                  request = new ActiveXObject('Microsoft.XMLHTTP');
                } catch (e) {}
            }
        }

        // State changes
        request.onreadystatechange = function() {
            if (request.readyState === 4) { // Done
                if (request.status === 200) { // Complete
                    response(request.responseText)
                }
                else
                    response();
            }
        }
        request.open('GET', what, true);
        request.setRequestHeader("Authorization", "Bearer " + spotifyAccessToken);
        request.send(null);
    }
}

//PromiseAll
var promiseAll = function(items, block, done, fail) {
    var self = this;
    var promises = [],
                   index = 0;
    items.forEach(function(item) {
        promises.Push(function(item, i) {
            return new Promise(function(resolve, reject) {
                if (block) {
                    block.apply(this, [item, index, resolve, reject]);
                }
            });
        }(item, ++index))
    });
    Promise.all(promises).then(function AcceptHandler(results) {
        if (done) done(results);
    }, function ErrorHandler(error) {
        if (fail) fail(error);
    });
}; //promiseAll

// LP: deferred execution block
var ExecutionBlock = function(item, index, resolve, reject) {
    var url = "https://api.spotify.com/v1/"
    url += item;
    console.log( url )
    SimpleRequest.call(url, function(result) {
        if (result) {

            var profileUrls = JSON.parse(result).playlists.items.map(function(item, index) {
                return item.owner.href;
            })
            resolve(profileUrls);
        }
        else {
            reject(new Error("call error"));
        }
    })
}

arr = [
    "search?type=playlist&q=%22Doom%20metal%22",
    "search?type=playlist&q=Adele"
]

promiseAll(arr, function(item, index, resolve, reject) {
    console.log("Making request [" + index + "]")
    ExecutionBlock(item, index, resolve, reject);
}, function(results) { // Aggregated results

    console.log("All profiles received " + results.length);
    //console.log(JSON.stringify(results[0], null, 2));

    ///// promiseall again

    var ExecutionProfileBlock = function(item, index, resolve, reject) {
        SimpleRequest.call(item, function(result) {
            if (result) {
                var obj = JSON.parse(result);
                resolve({
                    name: obj.display_name,
                    followers: obj.followers.total,
                    url: obj.href
                });
            } //result
        })
    } //ExecutionProfileBlock

    promiseAll(results[0], function(item, index, resolve, reject) {
        //console.log("Making request [" + index + "] " + item)
        ExecutionProfileBlock(item, index, resolve, reject);
    }, function(results) { // aggregated results
        console.log("All response received " + results.length);
        console.log(JSON.stringify(results, null, 2));
    }

    , function(error) { // Error
        console.log(error);
    })

    /////

  },
  function(error) { // Error
      console.log(error);
  });
<div id="console" />

Toto řešení jsem rozsáhle projednal zde .

70
loretoparisi

Odpověď 2017: nyní můžete v každém aktuálním prohlížeči a uzlu udělat přesně to, co chcete

To je velmi jednoduché:

  • Vraťte slib
  • Použijte 'await' , který poví JavaScriptu, aby čekal na slib, který má být vyřešen, do hodnoty (jako odpověď HTTP)
  • Přidání 'async' klíčové slovo do nadřazené funkce

Zde je pracovní verze kódu:

(async function(){

var response = await superagent.get('...')
console.log(response)

})()

čeká je podporováno ve všech aktuálních prohlížečích a uzlu 8

67
mikemaccana

Tuto vlastní knihovnu (napsanou pomocí Promise) můžete použít pro vzdálené volání.

function $http(apiConfig) {
    return new Promise(function (resolve, reject) {
        var client = new XMLHttpRequest();
        client.open(apiConfig.method, apiConfig.url);
        client.send();
        client.onload = function () {
            if (this.status >= 200 && this.status < 300) {
                // Performs the function "resolve" when this.status is equal to 2xx.
                // Your logic here.
                resolve(this.response);
            }
            else {
                // Performs the function "reject" when this.status is different than 2xx.
                reject(this.statusText);
            }
        };
        client.onerror = function () {
            reject(this.statusText);
        };
    });
}

Jednoduchý příklad použití:

$http({
    method: 'get',
    url: 'google.com'
}).then(function(response) {
    console.log(response);
}, function(error) {
    console.log(error)
});
60
Vinoth Rajendran

Js je jeden závit.

Prohlížeč lze rozdělit do tří částí:

1) Event Loop

2) Web API

3) Fronta událostí

Event Loop běží pro navždy, tj. Druh nekonečné smyčky.Event Queue je místo, kde jsou všechny vaše funkce tlačeny na nějaké události (například: click) to je jeden po druhém provedené z fronty a umístěny do Event loop, který vykonává tuto funkci a připravuje ji na sebe To znamená, že spuštění jedné funkce nezačne, dokud se funkce ve frontě neprovede ve smyčce události.

Přemýšlejme, že jsme ve frontě posunuli dvě funkce, jedna je pro získání dat ze serveru a druhá využívá tato data. Přepnuli jsme funkci serverRequest () ve frontě a pak funkci utiliseData (). serverRequest funkce jde do smyčky událostí a provádí volání na server, protože nikdy nevíme, kolik času bude trvat, než se data ze serveru, takže tento proces se očekává, že čas, a tak jsme zaneprázdněni naší události smyčky tak visí naše stránky, to je místo, kde Web API přijde do role, že vezme tuto funkci z smyčky událostí a zabývá se serverem, který dělá smyčku událostí zdarma, takže můžeme vykonat další funkci z fronty. Další funkcí ve frontě je utiliseData (), která jde do smyčky, ale protože nejsou k dispozici žádná data, to jde odpad a provedení další funkce pokračuje až do konce fronty (toto se nazývá Async volání, tj. můžeme udělat něco jiného, ​​než dostaneme data)

Předpokládejme, že naše funkce serverRequest () měla návratový kód v kódu, když získáme zpět data ze serveru Web API bude tlačit do fronty na konci fronty. Vzhledem k tomu, že se posunuje na konec ve frontě, nemůžeme využít jeho data, protože v naší frontě není žádná funkce pro využití těchto dat .Není tedy možné vrátit něco z Async Call.

Řešení tohoto problému je tedy {callback nebo slib.

Obrázek z jedné z odpovědí zde správně vysvětluje použití zpětného volání ... Dáváme funkci (funkce využívající data vrácená ze serveru) funkci volání serveru.

 CallBack

 function doAjax(callbackFunc, method, url) {
  var xmlHttpReq = new XMLHttpRequest();
  xmlHttpReq.open(method, url);
  xmlHttpReq.onreadystatechange = function() {

      if (xmlHttpReq.readyState == 4 && xmlHttpReq.status == 200) {
        callbackFunc(xmlHttpReq.responseText);
      }


  }
  xmlHttpReq.send(null);

}

V mém kódu se nazývá jako

function loadMyJson(categoryValue){
  if(categoryValue==="veg")
  doAjax(print,"GET","http://localhost:3004/vegetables");
  else if(categoryValue==="fruits")
  doAjax(print,"GET","http://localhost:3004/fruits");
  else 
  console.log("Data not found");
}

Přečtěte si zde nové metody v ECMA (2016/17) pro provádění asynchronního volání (@Felix Kling Odpověď nahoře) https://stackoverflow.com/a/14220323/7579856

55
Aniket Jha

Dalším řešením je provedení kódu přes sekvenční executor nsynjs .

Pokud je základní funkce promisified

nsynjs vyhodnotí postupně všechny sliby a výsledek slibuje do vlastnosti data:

function synchronousCode() {

    var getURL = function(url) {
        return window.fetch(url).data.text().data;
    };
    
    var url = 'https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js';
    console.log('received bytes:',getURL(url).length);
    
};

nsynjs.run(synchronousCode,{},function(){
    console.log('synchronousCode done');
});
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>

Pokud základní funkce není slibná

Krok 1. Funkce Wrap s zpětným zavoláním do nsynjs-aware wrapperu (pokud má slibnou verzi, můžete tento krok přeskočit):

var ajaxGet = function (ctx,url) {
    var res = {};
    var ex;
    $.ajax(url)
    .done(function (data) {
        res.data = data;
    })
    .fail(function(e) {
        ex = e;
    })
    .always(function() {
        ctx.resume(ex);
    });
    return res;
};
ajaxGet.nsynjsHasCallback = true;

Krok 2. Vložte synchronní logiku do funkce:

function process() {
    console.log('got data:', ajaxGet(nsynjsCtx, "data/file1.json").data);
}

Krok 3. Spusťte funkci synchronně přes nsynjs:

nsynjs.run(process,this,function () {
    console.log("synchronous function finished");
});

Nsynjs vyhodnotí všechny operátory a výrazy krok za krokem, pozastavení provádění v případě, že výsledek nějaké pomalé funkce není připraven.

Další příklady zde: https://github.com/amaksr/nsynjs/tree/master/examples

54
amaksr

Je to velmi běžný problém, kterému čelíme, když se potýkáme s „záhadami“ JavaScriptu. Dovolte mi zkusit toto tajemství dnes demystifikovat.

Začněme s jednoduchou funkcí JavaScriptu:

function foo(){
// do something 
 return 'wohoo';
}

let bar = foo(); // bar is 'wohoo' here

To je jednoduché synchronní volání funkce (kde každý řádek kódu je 'hotový s jeho prací' před příští v posloupnosti), a výsledek je stejný jak očekával.

Nyní přidáme trochu zkroucení tím, že v naší funkci zavedeme malé zpoždění, takže všechny řádky kódu nejsou „hotové“ v sekvenci. Bude tedy emulovat asynchronní chování funkce:

function foo(){
 setTimeout( ()=>{
   return 'wohoo';
  }, 1000 )
}

let bar = foo() // bar is undefined here

Takže tam jdete, že zpoždění jen zlomilo funkčnost, kterou jsme očekávali! Ale co se přesně stalo? Je to vlastně docela logické, když se podíváte na kód. funkce foo(), po provedení, nevrací nic (tedy vrácená hodnota je undefined), ale spustí časovač, který provede funkci po 1s, aby se vrátil 'wohoo'. Ale jak vidíte, hodnota, která je přiřazena baru, je okamžitě vrácený materiál z foo (), ne nic jiného, ​​co přijde později.

Jak tedy řešíme tento problém?

Zeptejte se naší funkce naSLIB. Slib je opravdu o tom, co to znamená: znamená to, že funkce vám zaručuje, že budete mít k dispozici jakýkoliv výstup, který se v budoucnu dostane. Podívejme se tedy na akci pro náš malý problém výše:

function foo(){
   return new Promise( (resolve, reject) => { // I want foo() to PROMISE me something
    setTimeout ( function(){ 
      // promise is RESOLVED , when execution reaches this line of code
       resolve('wohoo')// After 1 second, RESOLVE the promise with value 'wohoo'
    }, 1000 )
  })
}

let bar ; 
foo().then( res => {
 bar = res;
 console.log(bar) // will print 'wohoo'
});

Shrnutí je tedy - k řešení asynchronních funkcí, jako jsou volání na bázi iaxu atd., Můžete použít slib k resolve hodnotě (kterou hodláte vrátit). Tedy, v krátkosti jste vyřešit hodnota namísto vracející se , v asynchronních funkcích.

UPDATE (slibuje asynchronní/čekající)

Kromě použití then/catch pro práci se sliby existuje ještě jeden přístup. Myšlenkou je rozpoznat asynchronní funkci a pak čekat na sliby vyřešit, před přesunem na další řádek kódu. Pod kapotou je stále jen promises, ale s jiným syntaktickým přístupem. Pro lepší přehlednost můžete najít porovnání níže:

pak/verze úlovku:

function fetchUsers(){
   let users = [];
   getUsers()
   .then(_users => users = _users)
   .catch(err =>{
      throw err
   })
   return users;
 }

asynchronní/čekající verze:

  async function fetchUsers(){
     try{
        let users = await getUsers()
        return users;
     }
     catch(err){
        throw err;
     }
  }
42
Anish K.

ECMAScript 6 má „generátory“, které umožňují snadno programovat v asynchronním stylu.

function* myGenerator() {
    const callback = yield;
    let [response] = yield $.ajax("https://stackoverflow.com", {complete: callback});
    console.log("response is:", response);

    // examples of other things you can do
    yield setTimeout(callback, 1000);
    console.log("it delayed for 1000ms");
    while (response.statusText === "error") {
        [response] = yield* anotherGenerator();
    }
}

Chcete-li spustit výše uvedený kód, postupujte takto:

const gen = myGenerator(); // Create generator
gen.next(); // Start it
gen.next((...args) => gen.next([...args])); // Set its callback function

Pokud potřebujete cílit prohlížeče, které nepodporují ES6, můžete kód spustit přes Babel nebo kompilátorem pro uzavření ECMAScript 5.

Zpětné volání ...args jsou zabaleny do pole a jsou zničeny, když je čtete, aby se vzor mohl vyrovnat s zpětnými voláními, která mají více argumentů. Například s uzlem fs :

const [err, data] = yield fs.readFile(filePath, "utf-8", callback);
36
James

Zde jsou některé přístupy k práci s asynchronními požadavky:

  1. Objekt prohlížeče Promise
  2. Q - Knihovna slibů pro JavaScript
  3. A + Promises.js
  4. jQuery odloženo
  5. XMLHttpRequest API
  6. Použití konceptu zpětného volání - jako implementace v první odpovědi

Příklad: jQuery odložil implementaci pro práci s více požadavky

var App = App || {};

App = {
    getDataFromServer: function(){

      var self = this,
                 deferred = $.Deferred(),
                 requests = [];

      requests.Push($.getJSON('request/ajax/url/1'));
      requests.Push($.getJSON('request/ajax/url/2'));

      $.when.apply(jQuery, requests).done(function(xhrResponse) {
        return deferred.resolve(xhrResponse.result);
      });
      return deferred;
    },

    init: function(){

        this.getDataFromServer().done(_.bind(function(resp1, resp2) {

           // Do the operations which you wanted to do when you
           // get a response from Ajax, for example, log response.
        }, this));
    }
};
App.init();
36
Mohan Dere

Krátká odpověď : Vaše foo() metoda se okamžitě vrátí, zatímco volání $ajax() provede asynchronně po návratu funkce . Problém je v tom, jak nebo kam uložit výsledky načtené asynchronním voláním, jakmile se vrátí.

V této niti bylo uvedeno několik řešení. Snad nejjednodušší způsob je předat objekt metodě foo() a uložit výsledky v členu tohoto objektu po dokončení asynchronního volání.

function foo(result) {
    $.ajax({
        url: '...',
        success: function(response) {
            result.response = response;   // Store the async result
        }
    });
}

var result = { response: null };   // Object to hold the async result
foo(result);                       // Returns before the async completes

Všimněte si, že volání foo() vrátí nic užitečného. Výsledek asynchronního volání však bude nyní uložen v souboru result.response.

33
David R Tribble

Použijte funkci callback() uvnitř foo() úspěchu. Zkuste to takto. Je to jednoduché a snadno pochopitelné.

var lat = "";
var lon = "";
function callback(data) {
    lat = data.lat;
    lon = data.lon;
}
function getLoc() {
    var url = "http://ip-api.com/json"
    $.getJSON(url, function(data) {
        callback(data);
    });
}

getLoc();
33
Mahfuzur Rahman

Nacházíme se ve vesmíru, který vypadá, že postupuje podél dimenze, kterou nazýváme „čas“. Opravdu nechápeme, co je čas, ale vyvinuli jsme abstrakce a slovní zásobu, které nám daly důvod a hovořili o tom: "minulost", "přítomnost", "budoucnost", "před", "po".

Počítačové systémy, které stavíme - stále více - mají čas jako důležitý rozměr. Určité věci se připravují v budoucnu. Poté, co se tyto první věci nakonec objeví, se musí stát další věci. Toto je základní pojem nazvaný "asynchronicita". V našem stále více propojeném světě, nejběžnější případ asynchronicity čeká na nějaký vzdálený systém reagovat na nějaký požadavek.

Zvažte příklad. Zavoláte mlékař a objednejte si mléko. Když to přijde, chcete dát do své kávy. Teď si nemůžete dát mléko do kávy, protože tu ještě není. Budete muset počkat, až to přijde, než ji vložíte do kávy. Jinými slovy, následující nebude fungovat:

var milk = order_milk();
put_in_coffee(milk);

Protože JS nemá žádný způsob, jak vědět, že potřebuje počkat pro order_milk skončí dříve, než provede put_in_coffee. Jinými slovy, neví, že order_milk je asynchronní - je něco, co nebude mít za následek mléko až do budoucna. JS a další deklarativní jazyky provádějí jedno prohlášení za druhým bez čekání.

Klasický JS přístup k tomuto problému, využívající skutečnosti, že JS podporuje funkce jako prvotřídní objekty, které mohou být předávány kolem, je předávání funkce jako parametru asynchronnímu požadavku, který pak vyvolá, když je dokončen. v budoucnu. To je přístup "zpětného volání". Vypadá to takto:

order_milk(put_in_coffee);

order_milk zahajuje, objednává mléko, pak, když a jen když to přijde, vyvolá put_in_coffee.

Problém s tímto přístupem zpětného volání je v tom, že znečišťuje normální sémantiku funkce vykazující její výsledek pomocí return; funkce nesmí vykazovat své výsledky voláním zpětného volání zadaného jako parametr. Také tento přístup se může rychle chovat neprakticky, pokud se jedná o delší sekvence událostí. Řekněme například, že chci počkat, až bude mléko vloženo do kávy, a pak teprve poté provést třetí krok, konkrétně pití kávy. Nakonec musím napsat něco takového:

order_milk(function(milk) { put_in_coffee(milk, drink_coffee); }

kde přecházím na put_in_coffee jak mléko, které se do něj vloží, tak i akce (drink_coffee), která se má provést, jakmile je mléko vloženo. Takový kód je obtížné psát a číst a ladit.

V tomto případě bychom mohli kód v otázce přepsat takto:

var answer;
$.ajax('/foo.json') . done(function(response) {
  callback(response.data);
});

function callback(data) {
  console.log(data);
}

Zadejte sliby

To byla motivace k pojmu "slib", což je určitý typ hodnoty, který představuje budoucí nebo asynchronní výsledek nějakého druhu. Může to představovat něco, co se již stalo, nebo se to stane v budoucnu, nebo se nikdy nestane vůbec. Sliby mají jedinou metodu, pojmenovanou then, na kterou předáváte akci, která má být provedena, když výsledek, který slib představuje, byl realizován.

V případě našeho mléka a kávy navrhujeme order_milk vrátit slib pro přijíždějící mléko, poté specifikujeme put_in_coffee jako akci then takto:

order_milk() . then(put_in_coffee)

Jednou z výhod tohoto je, že můžeme tyto řetězce spojit a vytvořit sekvence budoucích výskytů („řetězení“):

order_milk() . then(put_in_coffee) . then(drink_coffee)

Pojďme aplikovat sliby na váš konkrétní problém. Logiku požadavků zabalíme do funkce, která vrací slib:

function get_data() {
  return $.ajax('/foo.json');
}

Vlastně jsme přidali return do volání $.ajax. To funguje, protože $.ajax jQuery již vrací podobnou věc. (V praxi, bez toho, abychom se dostali do podrobností, bychom raději zabalili toto volání tak, aby se vrátil skutečný slib, nebo použijte nějakou alternativu k $.ajax, která tak dělá.) Nyní, pokud chceme soubor načíst a čekat na jeho dokončení a pak něco udělat, můžeme jednoduše říci

get_data() . then(do_something)

například,

get_data() . 
  then(function(data) { console.log(data); });

Když používáme sliby, skončíme předáváním mnoha funkcí do then, takže je často užitečné používat kompaktnější funkce šipek ve stylu ES6:

get_data() . 
  then(data => console.log(data));

Klíčové slovo async

Ale stále je tu něco, co je nejistě nespokojené, že je třeba napsat kód jedním způsobem, pokud je synchronní a zcela odlišný, pokud je asynchronní. Pro synchronní píšeme

a();
b();

ale pokud je a asynchronní, slibujeme, že musíme napsat

a() . then(b);

Nahoře jsme řekli: „JS nemá žádný způsob, jak vědět, že potřebuje počkat pro první volání, které bude trvat dříve, než provede druhé“. Nebylo by hezké, kdyby tam byl nějaký způsob, jak říct JS, že? Ukazuje se, že existuje klíčové slovo await, které se používá uvnitř speciálního typu funkce nazvané "asynchronní" funkce. Tato funkce je součástí připravované verze ES, ale je již k dispozici v transpilerech, jako je Babel, s danými správnými předvolbami. To nám umožňuje jednoduše psát

async function morning_routine() {
  var milk   = await order_milk();
  var coffee = await put_in_coffee(milk);
  await drink(coffee);
}

Ve vašem případě byste mohli něco napsat

async function foo() {
  data = await get_data();
  console.log(data);
}
27
user663031

Samozřejmě existuje mnoho přístupů, jako je synchronní požadavek, slib, ale podle mých zkušeností bych měl použít přístup zpětného volání. Je přirozené asynchronní chování Javascriptu. Takže fragment kódu může být přepsán trochu jinak:

function foo() {
    var result;

    $.ajax({
        url: '...',
        success: function(response) {
            myCallback(response);
        }
    });

    return result;
}

function myCallback(response) {
    // Does something.
}
26
Khoa Bui

Otázkou bylo:

Jak vrátím odpověď z asynchronního volání?

které lze interpretovat jako:

Jak udělat asynchronní code look synchronní?

Řešením bude vyhnout se zpětným voláním a použít kombinaci Promises a async/await.

Chtěl bych uvést příklad pro žádost Ajax.

(Ačkoli to může být psáno v Javascript, já dávám přednost psát to v Python, a kompilovat to k Javascript používat Transcrypt . To bude jasné dost.)\T

Umožňuje nejprve povolit použití JQuery, aby $ bylo k dispozici jako S:

__pragma__ ('alias', 'S', '$')

Definujte funkci, která vrátí Promise, v tomto případě volání Ajax:

def read(url: str):
    deferred = S.Deferred()
    S.ajax({'type': "POST", 'url': url, 'data': { },
        'success': lambda d: deferred.resolve(d),
        'error': lambda e: deferred.reject(e)
    })
    return deferred.promise()

Použijte asynchronní kód, jako kdyby byl synchronní:

async def readALot():
    try:
        result1 = await read("url_1")
        result2 = await read("url_2")
    except Exception:
        console.warn("Reading a lot failed")
24

Pomocí ES2017 byste měli mít tuto funkci jako deklaraci funkce

async function foo() {
    var response = await $.ajax({url: '...'})
    return response;
}

A takhle.

(async function() {
    try {
        var result = await foo()
        console.log(result)
    } catch (e) {}
})()

Nebo syntaxe Promise

foo().then(response => {
    console.log(response)

}).catch(error => {
    console.log(error)

})
14

Spíše než házet kód na vás, tam jsou 2 pojmy, které jsou klíčem k pochopení, jak JS zpracovává zpětná volání a asynchronicity. (je to i slovo?)

Událostní smyčka a model souběžnosti

Musíte mít na paměti tři věci; Fronta; smyčka události a zásobník

V širokých, zjednodušujících termínech je smyčka událostí jako projektový manažer, neustále naslouchá všem funkcím, které chtějí běžet, a komunikuje mezi frontou a stackem.

while (queue.waitForMessage()) {
   queue.processNextMessage();
}

Jakmile přijme zprávu, spustí něco, co ji přidá do fronty. Fronta je seznam věcí, které čekají na provedení (jako vaše AJAX požadavek). představte si to takto:

 1. call foo.com/api/bar using foobarFunc
 2. Go perform an infinite loop
 ... and so on

Když se jedna z těchto zpráv provede, objeví se zpráva z fronty a vytvoří se zásobník, zásobník je vše, co JS potřebuje k provedení instrukce ve zprávě. Takže v našem příkladu se říká, že má volat foobarFunc

function foobarFunc (var) {
  console.log(anotherFunction(var));
}

Takže cokoliv, co foobarFunc potřebuje provést (v našem případě anotherFunction), se dostane do zásobníku. po spuštění a poté zapomenutí - smyčka události se pak přesune na další věc ve frontě (nebo si poslechne zprávy)

Klíčovou věcí je pořadí provedení. To je

KDY se něco spustí

Když zavoláte pomocí AJAX na externí stranu nebo spustíte jakýkoli asynchronní kód (například setTimeout), Javascript závisí na odezvě, než bude pokračovat.

Velkou otázkou je, kdy dostane odpověď? Odpověď zní, že nevíme - takže smyčka událostí čeká na to, že zpráva řekne "hej běž mě". Pokud JS jen čekal na tuto zprávu synchronně vaše aplikace by zmrazit a bude sát. JS tak pokračuje ve vykonávání další položky ve frontě, zatímco čeká, až se zpráva vrátí zpět do fronty.

Proto s asynchronními funkcemi používáme věci nazývané zpětné volání . Je to trochu jako slibovat doslova. Stejně jako v I slibuji, že něco vrátím jQuery používá specifická zpětná volání nazvaná deffered.donedeffered.fail a deffered.alwaysmimo jiné).

Takže to, co musíte udělat, je předat funkci, která je slíbena provést v určitém okamžiku s daty, které jsou předány na něj.

Protože zpětné volání není provedeno okamžitě, ale později je důležité předat odkaz na funkci, která nebyla provedena. tak

function foo(bla) {
  console.log(bla)
}

tak většinu času (ale ne vždy) projdete foo ne foo()

Doufejme, že to bude dávat smysl. Když se setkáte s takovými věcmi, které se zdají matoucí - vřele doporučuji, abyste si dokumentaci plně přečetli, abyste tomu alespoň porozuměli. Bude to mnohem lepší vývojář.

13
Matthew Brent

Podívejme se nejprve na les, než se podíváme na stromy.

Existuje mnoho informativních odpovědí s velkými detaily, nebudu opakovat žádné z nich. Klíčem k programování v JavaScriptu je nejprve správný mentální model celkového provedení.

  1. Váš vstupní bod (y) se provede jako výsledek události. Do prohlížeče je například načten tag skriptu s kódem. (Proto je třeba, že se budete muset zabývat připraveností stránky ke spuštění vašeho kódu, pokud vyžaduje, aby byly nejprve vytvořeny prvky dom. Atd.)
  2. Váš kód se provede až do dokončení - bez ohledu na to, jak mnoho asynchronních volání dělá - bez provedení any vašich zpětných volání, včetně požadavků XHR, nastavení časového limitu, obsluhy událostí domova atd. Každá z těchto zpětných volání, která čekají na provedení, bude sedět ve frontě a čekat, až se jejich řada rozběhne, když ostatní události, které vystřelily, mají hotové provedení.
  3. Každé individuální zpětné volání k požadavku XHR, nastavení časového limitu nebo dom události, která byla jednou vyvolána, se pak spustí až do dokončení.

Dobrou zprávou je, že pokud tomuto bodu dobře porozumíte, nikdy se nebudete muset starat o podmínky závodu. Měli byste v první řadě především, jak chcete kód zorganizovat jako v podstatě odpověď na různé diskrétní události a jak je chcete spojit do logické posloupnosti. Můžete použít sliby nebo vyšší úroveň async/await jako nástroje k tomuto účelu, nebo můžete použít svůj vlastní.

Ale neměli byste používat žádné taktické nástroje k vyřešení problému, dokud nebudete spokojeni se skutečnou problémovou doménou. Nakreslete mapu těchto závislostí, abyste věděli, co je třeba spustit, když. Pokus o ad-hoc přístup ke všem těmto zpětným voláním vám prostě nebude sloužit.

13
Haim Zamir

Použití příslibu

Nejdokonalejší odpověď na tuto otázku je použití Promise.

function ajax(method, url, params) {
  return new Promise(function(resolve, reject) {
    var xhr = new XMLHttpRequest();
    xhr.onload = function() {
      resolve(this.responseText);
    };
    xhr.onerror = reject;
    xhr.open(method, url);
    xhr.send(params);
  });
}

Používání

ajax("GET", "/test", "acrive=1").then(function(result) {
    // Code depending on result
})
.catch(function() {
    // An error occurred
});

Ale počkej...!

Existuje problém s použitím slibů!

Proč bychom měli používat svůj vlastní slib?

Použil jsem toto řešení na chvíli, než jsem přišel na to, že je chyba ve starých prohlížečích:

Uncaught ReferenceError: Promise is not defined

Rozhodl jsem se tedy implementovat vlastní třídu Promise pro ES3 na níže js kompilátory, pokud nejsou definovány. Stačí přidat tento kód před hlavní kód a pak bezpečnostní uživatel Promise!

if(typeof Promise === "undefined"){
    function _classCallCheck(instance, Constructor) {
        if (!(instance instanceof Constructor)) { 
            throw new TypeError("Cannot call a class as a function"); 
        }
    }
    var Promise = function () {
        function Promise(main) {
            var _this = this;
            _classCallCheck(this, Promise);
            this.value = undefined;
            this.callbacks = [];
            var resolve = function resolve(resolveValue) {
                _this.value = resolveValue;
                _this.triggerCallbacks();
            };
            var reject = function reject(rejectValue) {
                _this.value = rejectValue;
                _this.triggerCallbacks();
            };
            main(resolve, reject);
        }
        Promise.prototype.then = function then(cb) {
            var _this2 = this;
            var next = new Promise(function (resolve) {
                _this2.callbacks.Push(function (x) {
                    return resolve(cb(x));
                });
            });
            return next;
        };
        Promise.prototype.catch = function catch_(cb) {
            var _this2 = this;
            var next = new Promise(function (reject) {
                _this2.callbacks.Push(function (x) {
                    return reject(cb(x));
                });
            });
            return next;
        };
        Promise.prototype.triggerCallbacks = function triggerCallbacks() {
            var _this3 = this;
            this.callbacks.forEach(function (cb) {
                cb(_this3.value);
            });
        };
        return Promise;
    }();
}
12
Amir Forsati

Zde je příklad, který funguje:

const validateName = async userName => {
  const url = "abc/xyz";
  try {
    const response = await axios.get(url);
    return response.data
  } catch (err) {
    return false;
  }
};

validateName("user")
 .then(data => console.log(data))
 .catch(reason => console.log(reason.message))
7
Alex Montoya