2016-04-19 23 views
7

In fase di test, ho riscontrato che le Promesse JavaScript sono sempre asincrone indipendentemente dal fatto che contengano o meno funzioni asincrone nella loro catena.Perché javascript promette asincrono quando si chiamano solo funzioni sincrone?

Ecco un codice che mostra l'ordine delle operazioni in console. Se lo esegui, vedrai che anche se tutte le funzioni sono sincrone, l'output mostra entrambe le chiamate aPromise() eseguite in parallelo e "surprisingly this happens after run 2 finishes"non prima che termini 2.

function aPromise() { 
 
    return new Promise(function(resolve, reject) { 
 
    console.log("making promise A") 
 
    resolve(bPromise()); 
 
    console.log("promise A resolved") 
 
    }); 
 
} 
 

 

 
function bPromise() { 
 
    return new Promise(function(resolve, reject) { 
 
    console.log("making and resolving promise B") 
 
    resolve(); 
 
    }); 
 
} 
 

 
aPromise().then(function() { 
 
    console.log("finish run 1"); 
 
}).then(function() { 
 
    console.log("surprisingly this happens after run 2 finishes"); 
 
}); 
 
aPromise().then(function() { 
 
    console.log("finish run 2"); 
 
})

uscita per console:

making promise A 
making and resolving promise B 
promise A resolved 
making promise A 
making and resolving promise B 
promise A resolved 
finish run 1 
finish run 2 
surprisingly this happens after run 2 finishes 

Quindi, Perché JavaScript promette asincrono quando si chiama solo le funzioni sincrone? Cosa sta succedendo dietro le quinte che porta a questo comportamento?


P.S. Per comprenderlo meglio ho implementato il mio sistema Promise e ho scoperto che rendere le funzioni sincrone nell'ordine previsto era facile, ma farle accadere in parallelo era qualcosa che avrei potuto ottenere solo impostando un setTimeout() di pochi millisecondi ad ogni risolvere (La mia ipotesi è che questo non è ciò che sta accadendo con le promesse di vaniglia e che in realtà sono in multi-thread).

Questo è stato un piccolo problema per uno dei miei programmi in cui sto attraversando un albero applicando una serie di funzioni a ciascun nodo e mettendo le funzioni in coda se quel nodo ha già una funzione asincrona in esecuzione. La maggior parte delle funzioni sono sincrone, quindi la coda viene usata raramente ma al passaggio da callback (diavolo) a Promises ho riscontrato un problema in cui le code vengono utilizzate quasi sempre a causa di Promises che non vengono mai eseguite in modo sincrono. Non è un problema enorme, ma è un po 'un incubo di debug.

1 anno dopo EDIT

ho finito per scrivere del codice per affrontare questo. Non è straordinariamente accurato, ma l'ho usato con successo per risolvere il problema che stavo vivendo.

var SyncPromise = function(fn) { 
    var syncable = this; 
    syncable.state = "pending"; 
    syncable.value; 

    var wrappedFn = function(resolve, reject) { 
     var fakeResolve = function(val) { 
      syncable.value = val; 
      syncable.state = "fulfilled"; 
      resolve(val); 
     } 

     fn(fakeResolve, reject); 
    } 

    var out = new Promise(wrappedFn); 
    out.syncable = syncable; 
    return out; 
} 

SyncPromise.resolved = function(result) { 
    return new SyncPromise(function(resolve) { resolve(result); }); 
} 

SyncPromise.all = function(promises) { 
    for(var i = 0; i < promises.length; i++) { 
     if(promises[i].syncable && promises[i].syncable.state == "fulfilled") { 
      promises.splice(i, 1); 
      i--; 
     } 
     // else console.log("syncable not fulfilled" + promises[i].syncable.state) 
    } 

    if(promises.length == 0) 
     return SyncPromise.resolved(); 

    else 
     return new SyncPromise(function(resolve) { Promise.all(promises).then(resolve); }); 
} 

Promise.prototype.syncThen = function (nextFn) { 
    if(this.syncable && this.syncable.state == "fulfilled") { 
      // 
     if(nextFn instanceof Promise) { 
      return nextFn; 
     } 
     else if(typeof nextFn == "function") { 
      var val = this.syncable.value; 
      var out = nextFn(val); 
      return new SyncPromise(function(resolve) { resolve(out); }); 
     } 
     else { 
      PINE.err("nextFn is not a function or promise", nextFn); 
     } 
    } 

    else { 
     // console.log("default promise"); 
     return this.then(nextFn); 
    } 
} 
+2

Qualcun altro può probabilmente scavare discussioni specifiche, ma la filosofia generale era che la coerenza/prevedibilità è una virtù nella progettazione dell'interfaccia. Alcune delle prime implementazioni di Promise non si comportavano così, ma il sentimento cambiava man mano che le persone venivano morse da errori imprevisti. È molto più facile ragionare sulle Promesse in generale se sai che verranno sempre gestite in modo asincrono. –

+0

Sai che tipo di bug? –

+2

Sguardo rapido su [** the spec **] (http://www.ecma-international.org/ecma-262/6.0/#sec-promise-objects) e la dicitura "immediatamente" verrà accodata ** un lavoro da chiamare ", la prima volta che una cosa _queque_ può richiamare è immediatamente dopo la fine della funzione corrente. 'new Promise (r => r()). then (() => console.log ('yay')); let i = 10; while (--i) console.log (i); 'logs 9..1 before the yay –

risposta

9

Il callback passato a un costruttore promessa è sempre chiamato in modo sincrono, ma i callback passati in then sono sempre chiamati in modo asincrono (si potrebbe usare setTimeout con un ritardo di 0 in un'implementazione userland per raggiungere questo obiettivo).

Semplificare il vostro esempio (e dando i nomi della funzione anonima in modo da poter fare riferimento ad essi) a:

Promise.resolve().then(function callbackA() { 
    console.log("finish run 1"); 
}).then(function callbackB() { 
    console.log("surprisingly this happens after run 2 finishes"); 
}); 

Promise.resolve().then(function callbackC() { 
    console.log("finish run 2"); 
}) 

dà ancora l'uscita nello stesso ordine:

finish run 1 
finish run 2 
surprisingly this happens after run 2 finishes 

eventi accadono in questo ordine:

  1. La prima promessa si risolve (sincrono)
  2. callbackA viene aggiunto alla coda del ciclo di eventi
  3. La seconda promessa è risolto
  4. callbackC viene aggiunto alla coda del ciclo di eventi
  5. Non c'è niente da fare in modo che il ciclo di eventi si accede, callbackA è il primo nel coda in modo che venga eseguita, non restituisce una promessa quindi la promessa intermedia per il callbackB viene immediatamente risolta in modo sincrono, che aggiunge il callbackB alla coda del ciclo di eventi.
  6. Non vi è nulla da fare in modo che si acceda al loop degli eventi, callbackC è il primo nella coda in modo che venga eseguito.
  7. Non vi è nulla da fare in modo che si acceda al ciclo degli eventi, il callbackB è il primo nella coda in modo che venga eseguito.

Il modo più semplice che posso pensare per aggirare il problema è quello di utilizzare una libreria che ha una funzione di Promise.prototype.isFulfilled è possibile utilizzare per decidere se chiamare il secondo richiamata in modo sincrono o meno. Per esempio:

var Promise = require('bluebird');                               

Promise.prototype._SEPH_syncThen = function (callback) { 
    return (
     this.isPending() 
     ? this.then(callback) 
     : Promise.resolve(callback(this.value())) 
    ); 
} 

Promise.resolve()._SEPH_syncThen(function callbackA() { 
    console.log("finish run 1"); 
})._SEPH_syncThen(function callbackB() { 
    console.log("surprisingly this happens after run 2 finishes"); 
}); 

Promise.resolve()._SEPH_syncThen(function callbackC() { 
    console.log("finish run 2"); 
}) 

Questo uscite:

finish run 1 
surprisingly this happens after run 2 finishes 
finish run 2 
+0

Una coda di eventi fa sì che Promises agisca sempre in modo asincrono. Questo ha senso. Sai perché è stata fatta questa scelta progettuale? Era una specie di controintuitivo per me. –

+4

Risposta breve di @SephReed: coerenza. Risposta lunga: mettere in discussione le scelte progettuali di uno sviluppatore di lingue non rientra nell'ambito di Stack Overflow. –

+2

@SephReed Credo che ci sia un'interfaccia/API standard che avrà sempre un comportamento standard indipendentemente dal codice in esecuzione. Mutare la sua funzione a seconda dell'applicazione potrebbe causare comportamenti, complessità e confusione imprevisti. – ste2425

0

Il codice va bene è che si desidera che le promesse di eseguire in modo indipendente e far loro eseguire a modo loro, non importa ognuna completa prima. Non appena il codice è asincrono, non è possibile prevedere quale sarà completato per primo (a causa della natura asincrona dello event loop).

Tuttavia, se si desidera prendere tutte le promesse dopo che sono state completate, è necessario utilizzare Promise.all (che equivale a $.when è jQuery). See: