2016-03-03 27 views
6

Desidero elaborare una serie di promesse in Sequenza. Ho un working piece of code qui sotto, ma mi chiedo se ho complicato troppo la catena di promesse. Mi sembra di creare una grande quantità di nuove chiusure e sto grattando la mia testa chiedendomi se mi manca qualcosa.Sequenza Promessa Javascript

c'è un modo migliore di scrivere questa funzione:

'use strict'; 
addElement("first") 
.then(x => {return addElement("second")}) 
.then(x => { return addElement("third")}) 
.then(x => { return addElement("fourth")}) 

function addElement(elementText){ 
    var myPromise = new Promise(function(resolve,reject){ 
     setTimeout(function(){ 
      var element=document.createElement('H1'); 
      element.innerText = `${elementText} ${Date.now()}`; 
      document.body.appendChild(element); 
      resolve(); 
     }, Math.random() * 2000); 
    }); 
return myPromise; 
} 
+3

le funzioni direzionali potrebbero essere semplificate - '.then (x => addElement ("secondo"))' - Allo stesso modo si potrebbero utilizzare le funzioni di direzione in 'addElement' - ma io non sono sicuro perché si pensa stai creando "una grande quantità di nuove chiusure" –

+0

Ho incontrato anche questo problema e ho finito con l'uso di 'bind', anche se sembra altrettanto disordinato (ma evita i wrapper di funzioni extra):' .then (addElement.bind (null, "second")) ', ecc. –

+0

Basta chiedersi se ci sono oggetti promessi ridondanti creati qui. Qualcosa come te crea 6 oggetti di promessa quando 3 sarebbe sufficiente? Quindi crea già un oggetto promesso che non puoi riutilizzare? Fammi pensare che potrei sbagliarmi. – Nishant

risposta

3

il codice sia vicino alla migliore che si può arrivare qui. Le promesse possono essere una strana struttura su cui abituarsi, soprattutto perché la scrittura di un codice promesso può spesso finire per incorporare una funzione in un'altra funzione. Come si può vedere here, questo è un fraseggio piuttosto comune da usare. Ci sono solo due cambiamenti stilistici che potrebbero essere fatti. Innanzitutto, myPromise non è necessario e serve solo per aggiungere una riga di codice aggiuntiva confusa. È più semplice restituire la promessa direttamente. In secondo luogo, è possibile utilizzare il binding di funzioni per semplificare le chiamate all'inizio. Potrebbe non essere all'interno della funzione stessa, ma elimina molte chiusure. vengono visualizzate sia le modifiche di seguito:

'use strict'; 
addElement("first") 
.then(addElement.bind(null,"second")) 
.then(addElement.bind(null,"third")) 
.then(addElement.bind(null,"fourth")) 

function addElement(elementText){ 
    return new Promise(function(resolve,reject){ 
     setTimeout(function(){ 
      var element=document.createElement('H1'); 
      element.innerText = `${elementText} ${Date.now()}`; 
      document.body.appendChild(element); 
      resolve(); 
     }, Math.random() * 2000); 
    }); 
} 

Vale la pena sottolineare che, se erano disposti a ristrutturare un po ', un design leggermente più attraente avrebbe preso forma:

'use strict'; 
var myWait = waitRand.bind(null,2000); 
myWait 
    .then(addElement.bind(null, "first")) 
    .then(myWait) 
    .then(addElement.bind(null, "second")) 
    .then(myWait) 
    .then(addElement.bind(null, "third")) 

function waitRand(millis) { 
    return new Promise((resolve, reject) => { 
    setTimeout(resolve, Math.random() * millis); 
    } 
} 

function addElement(elementText) { 
    var element = document.createElement('h1'); 
    element.innerText = `${elementText} ${Date.now()}`; 
    document.body.appendChild(element); 
} 

Questo scambia lunghezza della catena di promessa per chiarezza, oltre ad avere un numero leggermente inferiore di livelli.

6

@TheToolBox ha una buona risposta per voi.

Solo per divertimento, ho intenzione di mostrarvi una tecnica alternativa che utilizza i generatori che trae ispirazione da coroutines.

Promise.prototype.bind = Promise.prototype.then; 

const coro = g => { 
    const next = x => { 
    let {done, value} = g.next(x); 
    return done ? value : value.bind(next); 
    } 
    return next(); 
} 

Usando questo, il codice sarà simile a questa

const addElement = elementText => 
    new Promise(resolve => { 
    setTimeout(() => { 
     var element = document.createElement('H1'); 
     element.innerText = `${elementText} ${Date.now()}`; 
     document.body.appendChild(element); 
     resolve(); 
    }, Math.random() * 2000); 
    }); 

coro(function*() { 
    yield addElement('first'); 
    yield addElement('second'); 
    yield addElement('third'); 
    yield addElement('fourth'); 
}()); 

Ci sono alcune cose molto interessanti che si possono fare con i generatori di promesse. Non sono immediatamente evidenti qui perché la tua promessa addElement non risolve alcun valore effettivo.


Se effettivamente resolve alcuni valori, si potrebbe fare qualcosa di simile

// sync 
const appendChild = (x,y) => x.appendChild(y); 

// sync 
const createH1 = text => { 
    var elem = document.createElement('h1'); 
    elem.innerText = `${text} ${Date.now()}`; 
    return elem; 
}; 

// async 
const delay = f => 
    new Promise(resolve => { 
    setTimeout(() => resolve(f()), Math.random() * 2000); 
    }); 

// create generator; this time it has a name and accepts an argument 
// mix and match sync/async as needed 
function* renderHeadings(target) { 
    appendChild(target, yield delay(() => createH1('first'))); 
    appendChild(target, yield delay(() => createH1('second'))); 
    appendChild(target, yield delay(() => createH1('third'))); 
    appendChild(target, yield delay(() => createH1('fourth'))); 
} 

// run the generator; set target to document.body 
coro(renderHeadings(document.body)); 

Degne di nota, createH1 e appendChild sono funzioni sincrone. Questo approccio consente effettivamente di concatenare le normali funzioni e sfocare le linee tra ciò che è sincronizzato e ciò che è asincrono. Esegue anche/si comporta esattamente come il codice che hai originariamente pubblicato.

Quindi sì, questo ultimo esempio di codice potrebbe essere leggermente più interessante.


Infine,

Un vantaggio del coroutine ha sul .then concatenamento, è che tutte le promesse risolti possibile accedere all'interno della stessa portata.

Confronta .then catene ...

op1() 
    .then(x => op2(x)) 
    .then(y => op3(y)) // cannot read x here 
    .then(z => lastOp(z)) // cannot read x or y here 

al coroutine ...

function*() { 
    let x = yield op1(); // can read x 
    let y = yield op2(); // can read x and y here 
    let z = yield op3(); // can read x, y, and z here 
    lastOp([x,y,z]);  // use all 3 values ! 
} 

Naturalmente ci sono workarounds per questo che utilizzano le promesse, ma oh ragazzo vuol ottenere brutto veloce ...


Se sei interessato a utilizzare i generatori in questo modo, consiglio vivamente di dare un'occhiata al progetto co.

Ed ecco un articolo, Callbacks vs Coroutines, dal creatore di co, @tj.

In ogni caso, spero che hai avuto divertente l'apprendimento di alcune altre tecniche ^__^

3

Si potrebbe semplificare l'utilizzo della funzione facendo addElement() ritorno di una funzione, invece in modo che possa essere inserito direttamente in .then() gestori senza dovendo creare la funzione anonima:

'use strict'; 
addElement("first")() 
    .then(addElement("second")) 
    .then(addElement("third")) 
    .then(addElement("fourth")) 

function addElement(elementText){ 
    return function() { 
     return new Promise(function(resolve){ 
      setTimeout(function(){ 
       var element=document.createElement('H1'); 
       element.innerText = `${elementText} ${Date.now()}`; 
       document.body.appendChild(element); 
       resolve(); 
      }, Math.random() * 2000); 
     }); 
    } 
} 
1

Non c'è molto da fare per quanto riguarda il numero di chiusure. L'annidamento di funzioni è qualcosa a cui ci si abitua con js e il codice nella domanda non è poi così male.

Come altri hanno già detto, la scrittura di addElement() per restituire una funzione costituisce una catena principale promettente.

Se si procede leggermente oltre, si potrebbe considerare di scrivere la funzione restituita con una catena di promessa interna, consentendo la (leggera) separazione della risoluzione di promessa dall'inserimento dell'elemento DOM. Questo non crea più né meno chiusure, ma è sintatticamente più ordinato, in particolare permettendoti di scrivere setTimeout(resolve, Math.random() * 2000);.

'use strict'; 
addElement("first") 
.then(addElement("second")) 
.then(addElement("third")) 
.then(addElement("fourth")); 

function addElement(elementText) { 
    return function() { 
     return new Promise(function(resolve, reject) { 
      setTimeout(resolve, Math.random() * 2000); 
     }).then(function() { 
      var element = document.createElement('H1'); 
      document.body.appendChild(element); 
      element.innerText = `${elementText} ${Date.now()}`; 
     }); 
    }; 
} 

forse è solo me, ma trovo questo molto più piacevole per gli occhi, anche se a costo di un ulteriore .then(), quindi una promessa ulteriore, per addElement().

Nota: se è necessario risolvere la promessa con un valore, si è ancora concessa l'opportunità di farlo restituendo un valore dalla richiamata concatenata.

Andando ancora di più, se si desidera che gli elementi inseriti a comparire nel richiesto ordine, non l'ordine determinato da insediamento promessa, quindi è possibile creare/inserire elementi in modo sincrono, e li popolano in modo asincrono:

function addElement(elementText) { 
    var element = document.createElement('H1'); 
    document.body.appendChild(element); 
    return function() { 
     return new Promise(function(resolve, reject) { 
      setTimeout(resolve, Math.random() * 2000); 
     }).then(function() { 
      element.innerText = `${elementText} ${Date.now()}`; 
     }); 
    }; 
} 

Tutto ciò che era necessario era spostare due righe all'interno di addElement(), per modificare i tempi degli inserimenti lasciando la linea element.innerText = ... dov'era. Questo è possibile se si opta o meno per la catena della promessa interiore.

+0

La prima chiamata a 'addElement()' richiede un altro '()' dopo di esso per chiamare effettivamente la funzione interna (come mostrato nella mia risposta). E non hai bisogno di questa promessa in più per far inserire gli articoli nell'ordine richiesto. Questo è già fatto da altre soluzioni. Le funzioni interne sono già chiamate in ordine richiesto. – jfriend00

3

Io non so perché gli altri lasciati fuori un modo semplice fuori, si può semplicemente utilizzare un array e reduce metodo

let promise, inputArray = ['first', 'second', 'third', 'fourth']; 

promise = inputArray.reduce((p, element) => p.then(() => addElement(element)), Promise.resolve()); 
+1

Non è necessario assegnare 'p = p.then (...' –

0

ho scritto due metodi qui:

Sequence = { 
    all(steps) { 
     var promise = Promise.resolve(), 
      results = []; 

     const then = i => { 
      promise = promise.then(() => { 
       return steps[ i ]().then(value => { 
        results[ i ] = value; 
       }); 
      }); 
     }; 

     steps.forEach((step, i) => { 
      then(i); 
     }); 

     return promise.then(() => Promise.resolve(results)); 
    }, 
    race(steps) { 
     return new Promise((resolve, reject) => { 
      var promise = Promise.reject(); 

      const c = i => { 
       promise = promise.then(value => { 
        resolve(value); 
       }).catch(() => { 
        return steps[ i ](); 
       }); 
      }; 

      steps.forEach((step, i) => { 
       c(i); 
      }); 

      promise.catch(() => { 
       reject(); 
      }); 
     }); 
    } 
}; 

Sequence.all volontà eseguire le funzioni in una sequenza fino a quando tutte le promesse degli argomenti non vengono risolte. E restituire un altro oggetto Promise con argomenti come una matrice riempita con tutti i valori risolti in sequenza.

Sequence.all([() => { 
    return Promise.resolve('a'); 
},() => { 
    return Promise.resolve('b'); 
} ]).then(values => { 
    console.log(values); // output [ 'a', 'b' ] 
}); 

Sequence.race verrà eseguito funzioni in una sequenza e smettere di correre, mentre un oggetto promessa stato risolto.

Sequence.race([() => { 
    return Promise.reject('a'); 
},() => { 
    return Promise.resolve('b'); 
},() => { 
    return Promise.resolve('c'); 
} ]).then(values => { 
    console.log(values); // output [ 'a' ] 
});