2016-03-10 15 views
9

quindi ho una collezione di messaggiCome usare la promessa per evitare l'inferno di callback?

{ 
    id: String, 
    comments: [String], # id of Comments 
    links: [String], #id of Links 
} 

Commenti: { id: String, commento: String, }

vicini: { id: String, link: String, }

Trova un post con commenti e collegamenti ad esso relativi id:

Posts.findOne({id: id}, function(post) { 
    Comments.find({id: post.id}, function(comments) { 
    Links.find({id: post.id}, function(links) { 
     res.json({post: post, comments: comment, links: links}) 
    }) 
    }) 
}) 

Come utilizzare Promise (http://mongoosejs.com/docs/promises.html) per evitare il callback dell'inferno?

var query = Posts.findOne({id: id}); 
var promise = query.exec(); 

promise.then(function (post) { 
    var query1 = Comments.find({id: post.id}); 
    var promise1 = query1.exec(); 
    promise1.then(function(comments) { 
    var query2 = Links.find({id: post.id}); 
    var promise2 = query2.exec(); 
    promise2.then(function(links) { 
     res.json({post: post, comments: comment, links: links}) 
    }) 
    }) 
}); 

sembra esserci buone ......

risposta

4

Prova di Pentecoste questo:

function getPost(id) { 
    return Post 
    .findOne({id: id}) 
    .then(post => { 
     return post; 
    }); 
} 

utilizzando Q modulo

function getCommentsAndLinks(post) { 
    return Q.all([ 
    Comment.find({id: post.id}), 
    Links.find({id: post.id}) 
    ]) 
    .done(results => { 
    let comments = results[0]; 
    let links = results[1]; 
    return [post, comments, links]; 
    }) 
    .catch(err => { 
    // handle err 
    }) 

sul controller

getPost(postId) 
.then(getCommentsAndLinks) 
.then(results => { 
    let post = results[0]; 
    let comments = results[1]; 
    let links = results[2]; 
    // more code here 
}) 
.catch(err => { 
// handle err 
}) 

ma io suggerisco di non aver salvato serie di IDS, salvare l'istanza di oggetto, in modo da poter utilizzare Popola per ottenere tutti i dati di commenti e link, qualcosa di simile:

Post 
.findOne({id: id}) 
.populate('comments') 
.populate('links') 
.then(post => { 
    // here have the post with data of comments and links 
}); 
+0

@Sato - La prima versione qui serializza 'getComments()' e 'getLinks()' che non è necessario. Eseguirli in parallelo (come nella mia risposta) probabilmente andrà meglio. Inoltre, assicurati di capire come eseguire la gestione degli errori nella tua versione non promessa o promessa. – jfriend00

+0

@ jfriend00 hai ragione, ho modificato la mia risposta usando il modulo [Q] (https://www.npmjs.com/package/q), inoltre puoi usare 'Promise.all()' se preferisci. – DJeanCar

1

si può fare utilizzando promesse come questo:

Posts.findOne({id: id}).exec().then(function(post) { 
    let p1 = Comments.find({id: post.id}).exec(); 
    let p2 = Links.find({id: post.id}).exec(); 
    return Promise.all([p1, p2]).then(function(results) { 
     res.json({post: post, comments: results[0], links: results[1]}); 
    }); 
}).catch(function(err) { 
    // error here 
}); 

Questo imposta due operazioni Comments.find().exec() e Links.find().exec() che entrambi dipendono dalla post variabile, ma sono indipendenti l'una dall'altra in modo che possano essere eseguite in parallelo. Quindi, utilizza Promise.all() per sapere quando sono terminati entrambi e quindi è possibile emettere il JSON.

Ecco la descrizione passo passo:

  1. Run Posts.findOne().exec().
  2. Al termine, avviare sia Comments.find().exec() sia Links.find().exec() in parallelo.
  3. Utilizzare Promise.all() per sapere quando entrambi sono stati completati.
  4. Al termine di entrambi, emettere il JSON.

questo potrebbe essere fatto con meno di nidificazione, ma perché si sta utilizzando i risultati precedenti in richieste successive o nella JSON finale, è più facile per nidificare un po '.

È possibile visualizzare varie opzioni per la condivisione dei risultati precedenti mentre si concatenano richieste di promessa in questa altra risposta How to chain and share prior results.


FYI, dove questa implementazione di promessa brilla davvero rispetto a ciò che mostri nella tua domanda è per la gestione degli errori. Il tuo codice non promesso non mostra alcuna gestione degli errori, ma la versione promessa propagherà tutti gli errori fino al gestore .catch() per te.

1

Il vantaggio di utilizzare promesse è poterli catena, in modo che il codice può essere ridotto a:

let post, comments; 
Posts.findOne({id: id}).exec().then(_post => { 
    post = _post; 
    return Comments.find({id: post.id}).exec(); 
    }).then(_comments => { 
    comments = _comments; 
    return Links.find({id: post.id}).exec(); 
    }).then(links => res.json({post, comment, links})) 
    .catch(error => res.error(error.message)); 

si sarebbe accorto che avevo bisogno di un solo blocco catch.

+1

È meglio evitare [tale schema] (http://stackoverflow.com/a/28250700/1048572). – Bergi

2

Si stanno annidando le richiamate. Non hai bisogno di farlo.Se si restituisce una promessa da .then allora ogni .then si concatenano ad esso saranno risolti quando che promessa viene risolto:

promise.then(post => Comments.find({id: post.id}) 
    .then(comments => Links.find({id: post.id}) 
    .then(links => {}); 

La query commenti non dipende da link quindi in realtà si può fare entrambe le query in una sola volta:

promise.then(post => { 
    return Promise.all([ 
    post, 
    Comments.find({id: post.id}), 
    Links.find({id: post.id}), 
    ]); 
}).then(data => res.json({ 
    post: data[0], 
    comments: data[1], 
    links: data[2], 
}); 

Se si utilizza una libreria come bluebird è anche possibile usare qualcosa come l'operatore spread per rendere i nomi più trasparenti.


Vorrei anche considerare di usare co per il flusso di controllo del generatore a base di come credo che questo è ancora più chiaro:

co(function*() { 
    const post = yield Posts.findOne({id}); 
    const [comments, links] = yield [ 
    Comments.find({id: post.id}), 
    Links.find({id: post.id}), 
    ]; 

    res.json({post, comments, links}); 
}); 
+0

Un modo intelligente per passare la variabile 'post' al successivo' .then() 'handler passandola in' Promise.all() '. – jfriend00

0

Ecco una versione po 'più breve

Posts.findOne({id: id}).then(function (post) { 
    var query1 = Comments.find({id: post.id}); 
    var query2 = Links.find({id: post.id}); 

    Promise.all(query1.exec(), query2.exec()).then(function(data) { 
    res.json({ post: post, comments: data[0], links: data[1] }); 
    }); 
}); 
+0

Questo è praticamente identico alla mia risposta. – jfriend00

+0

Sì, scusa, quando ho iniziato a rispondere, la tua risposta non è stata ancora pubblicata. L'ho visto solo dopo aver postato il mio. – gabesoft

-2

a mio parere, non si può evitare di callback l'inferno. È la natura della programmazione asincrona. Dovresti sfruttare la programmazione asincrona, non cercare di renderla simile alla sincronia.

È necessario utilizzare la richiamata per creare una promessa, solo per ottenere la sintassi "allora". La sintassi "then" sembra migliore, ma in realtà non fornisce nulla di utile rispetto al callback, perché preoccuparsi. L'unica caratteristica utile della promessa è Promise.all, che puoi utilizzare per attendere la fine di tutte le tue promesse.

Provare a utilizzare rxjs per la gestione dei problemi asincroni. Devi ancora usare callback per creare un osservabile rxjs. Ma rxjs offre molte funzionalità per aiutarti a sfruttare la programmazione asincrona, non per evitarlo.

+0

le risposte sopra mostrano che è possibile e desiderabile evitare l'inferno di callback con promesse. vedere la risposta da @Explosion Pills – pungggi

Problemi correlati