2015-07-27 8 views
5

Nel tentativo di smettere di scrivere un sacco di codice duplicato, sto cercando di non aprire le chiamate. Preferirei preferibilmente passare solo le funzioni da un livello superiore. Come questo.Scrivere promesse senza aprire quindi chiama

function ensureLink(srcPath, dstPath){ 
    dstPath = fsRedux.predictDir(srcPath, dstPath) 
    var dstDir = path.dirname(dstPath) 
    return fsRedux.exists(dstPath) 
    .then(_.partialRight(fsRedux.ifFalseThrow, false, new Error(dstPath+" exists cannot ensure link"))) 
    .then(_.bind(fsExtra.mkdirsAsync, fsExtra, dstDir)) 
    .then(_.bind(_.bind(fsExtra.linkAsync, fsExtra, srcPath, dstPath))) 
} 

Tuttavia, il codice sopra riportato non funziona. Il test seguente mostra che non è possibile passare una funzione asincrona boundpromisifyAll. Il motivo è che il valore di allora è passato in queste promesse che li induce ad agire come argomento successivo nella chiamata, per queste funzioni che significa che attivano come callback, motivo per cui il primo errore di test è fuori con Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test. in moka.

var chai = require("chai") 
var chaiAsPromised = require("chai-as-promised") 
chai.use(chaiAsPromised) 
chai.should() 

var path = require("path") 
var _ = require("lodash") 
var mock = require("mock-fs") 

var Promise = require("bluebird") 
var fsRedux = require("./fs-redux") 
var fsExtra = Promise.promisifyAll(require("fs-extra")) 
var fs = Promise.promisifyAll(require("fs")) 

mock({ 
    'path/hello-alpha.txt': 'file content here', 
    'path/hello-beta.txt': 'file content here' 
}) 

var dstPath = "path/to/fake/dir/" 

function closedThen(srcPath, dstPath){ 
    dstPath = fsRedux.predictDir(srcPath, dstPath) 
    var dstDir = path.dirname(dstPath) 
    return fsRedux.exists(dstPath) 
    .then(_.partialRight(fsRedux.ifFalseThrow, false, new Error(dstPath+" exists cannot ensure link"))) 
    .then(_.bind(fsExtra.mkdirsAsync, fsExtra, dstDir)) 
    .then(_.bind(_.bind(fsExtra.linkAsync, fsExtra, srcPath, dstPath))) 
} 

function openThen(srcPath, dstPath){ 
    dstPath = fsRedux.predictDir(srcPath, dstPath) 
    var dstDir = path.dirname(dstPath) 
    return fsRedux.exists(dstPath) 
    .then(_.partialRight(fsRedux.ifFalseThrow, false, new Error(dstPath+" exists cannot ensure link"))) 
    .then(function(){ 
     return _.bind(fsExtra.mkdirsAsync, fsExtra, dstDir)() 
    }) 
    .then(function(){ 
     return _.bind(fsExtra.linkAsync, fsExtra, srcPath, dstPath)() 
    }) 
} 

describe("issue", function(){ 
    describe("closedThen()", function(){ 
    it("should return then and run promise", function(){ 
     return closedThen("path/hello-alpha.txt", dstPath).then(function(){ 
     return fsExtra.readFileAsync("path/to/fake/dir/hello-alpha.txt", "utf8").should.eventually.equal("file content here") 
     }) 
    }) 
    }) 
    describe("openThen()", function(){ 
    it("should return then and run promise", function(){ 
     return openThen("path/hello-beta.txt", dstPath).then(function(){ 
     return fsExtra.readFileAsync("path/to/fake/dir/hello-beta.txt", "utf8").should.eventually.equal("file content here") 
     }) 
    }) 
    }) 
}) 

Quali funzioni esistono, o il modo di avvolgere la funzione associata consentirebbe di lavorare con le promesse in questo modo?

Aggiornamento:

Sto cercando una libreria di suite di involucri funzione (lodash ha un sacco di loro) che permettono una facile interfaccia di partialing o binding from my question earlier o di confezionamento funzioni per passare ad una then o correre all'interno di un Promise.reduce. In questo modo concatenare le promesse è davvero facile.

Idealmente, voglio solo sapere come far funzionare la linea, un modo di avvolgerla in modo che quando viene passato un risultato dall'altra, lo ignori. O un'alternativa raccomandata.

.then(_.bind(fsExtra.mkdirsAsync, fsExtra, dstDir)) 
+4

Davvero difficile capire esattamente cosa stai chiedendo - puoi chiarire ulteriormente. Una funzione promessa (in qualche modo per definizione) restituisce una promessa e non utilizza più una richiamata. Se lo chiami, restituirà una promessa che viene risolta solo quando viene eseguita l'operazione asincrona sottostante. Inoltre, se si desidera un'operazione seriale, non un'operazione parallela, è necessario passare un riferimento di funzione a un gestore '.then()', non al valore di ritorno dell'esecuzione di qualche funzione (a meno che il valore restituito sia esso stesso una funzione che è ciò che vuoi chiamato in sequenza). – jfriend00

+2

Purtroppo, la modifica non ha aiutato molto a capire il problema.C'è una serie di descrizioni di problemi diversi confusi. Vedo almeno 1. il test dell'unità non è stato costruito correttamente e si traduce in un timeout (non un'affermazione fallita) 2. fraintendimento del funzionamento di 'bind'. in realtà per esempio last' then' handler '_.bind (fsExtra.linkAsync ...' riceve il risultato della precedente promessa in chain.Ma 'fsExtra.linkAsync' lo ottiene come terzo parametro (dopo' dstPath') –

+2

I don Forse ti basterà eseguire il backup e descrivere una sequenza specifica di operazioni che vuoi scriptare e poi chiedere come è il modo più efficiente per scriverlo? Sembra che tu stia cercando di fare una sorta di domanda generica, ma usando un insieme di codice davvero complicato con un sacco di problemi per cercare di descrivere il problema e ci stai perdendo in questa complessità. Puoi ridurre il problema ad un esempio molto più semplice? – jfriend00

risposta

0

Ecco un esempio utilizzando Ramda.

var Promise = require("bluebird") 
var R = require("ramda") 
var path = require("path") 
var fs = Promise.promisifyAll(require("fs")) 
var fse = Promise.promisifyAll(require("fs-extra")) 

function ifThrow(value, desiredValue, error){ 
    if(value == desiredValue) throw error 
    return value 
} 

var fsEnsureLink = function(srcpath, dstpath){ 
    return R.pipeP.apply(null, [ 
    R.always(fs.lstatAsync(srcpath).then(R.T, R.F)), 
    R.partialRight(ifThrow, false, new Error("source path does not exist")), 
    R.always(fs.lstatAsync(dstpath).then(R.T, R.F)), 
    R.partialRight(ifThrow, true, new Error("destination path exists")), 
    R.always(fse.mkdirsAsync(path.dirname(dstpath))), 
    R.always(fs.linkAsync(srcpath, dstpath)), 
    R.T, 
    ])() 
} 

fsEnsureLink("./package.json", "./test/package.json") 
// promise -> true || Error thrown 
0

Ecco tre funzioni che consentono di scrivere solo promesse profonde con un solo metodo. Usano il modulo dotty per modificare un oggetto impostato su un ambito globale al di fuori della catena di promessa.

Con wrap è possibile ignorare il valore corrente della catena.

function wrap(fn){ 
    return function(){ 
    return fn() 
    } 
} 

Con exportResult è possibile memorizzare il valore per un uso successivo.

function exportResult(obj, property){ 
    return function(value){ 
    dotty.put(obj, property, value) 
    return value 
    } 
} 

Con provideResult è possibile restituire un valore risultato precedente per tornare alla prossima chiamata then.

function provideResult(obj, property){ 
    return function(){ 
    return dotty.get(obj, property) 
    } 
} 

Risultato in qualcosa di simile.

function ensureLink(srcPath, dstPath){ 
    dstPath = fsRedux.predictDir(srcPath, dstPath) 
    var dstDir = path.dirname(dstPath) 
    var values = {} 
    return fsRedux.exists(dstPath) 
    .then(exportResult(values, "exists")) 
    .then(_.partialRight(fsRedux.ifFalseThrow, false, new Error(dstPath+" exists cannot ensure link"))) 
    .then(wrap(_.bind(fsExtra.mkdirsAsync, fsExtra, dstDir))) 
    .then(wrap(_.bind(fsExtra.linkAsync, fsExtra, srcPath, dstPath))) 
    .then(provideResult(values, "exists")) 
    // continue the chain provide result from exists... 
} 

Originariamente cercavo una soluzione più elegante a questo. O raccomandazioni su uno standard corrente che fa questo.

+0

Si potrebbe dare un'occhiata a [Ramda] (http://ramdajs.com/) – Bergi

+0

Sto trovando Ramda davvero interessante ma non così utile quando si tratta di aiutare a creare catene di promesse complesse e gestire il flusso di controllo. – ThomasReggi

+0

Hai dato un'occhiata a 'curryN',' composeP'? Anche il tuo 'wrap' potrebbe essere fatto usando' nAry'. – Bergi

Problemi correlati