2015-07-27 14 views
5

Starając się przerwać pisanie wielu duplikatów kodu, próbuję nie otwierać, a następnie dzwonić. Preferowałbym jedynie przekazywanie funkcji z poziomu najwyższego poziomu. Lubię to.Pisząc obietnice bez otwierania, a następnie dzwoniąc pod numer

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))) 
} 

Jednak powyższy kod nie działa. Poniższy test pokazuje, że nie można przekazać funkcji asynchronicznej boundpromisifyAll. Powodem jest to, że wartość ta jest przekazywana do tych obietnic, co powoduje, że stają się one następnym argumentem w wywołaniu, ponieważ te funkcje oznaczają, że są uruchamiane jako wywołania zwrotne, dlatego pierwszy błąd testowy jest wyświetlany z mokrą wersją z Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test..

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") 
     }) 
    }) 
    }) 
}) 

Jakie funkcje istnieją lub sposób owijania oprawionej funkcji pozwoliłby na pracę z obietnicami w ten sposób?

Aktualizacja:

szukam biblioteki pakietu owijarki funkcyjnych (lodash ma kilka z nich), które pozwalają na łatwy interfejs partialing lub binding from my question earlier lub owijania funkcji przejść do then lub biegu w obrębie Promise.reduce. W ten sposób spieranie obietnic jest naprawdę łatwe.

Idealnie po prostu chcę wiedzieć, jak uruchomić linię, sposób owijania jej tak, aby po przejściu z niej wynik zignorował ją. Lub zalecana alternatywa.

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

Naprawdę trudno dokładnie zrozumieć, o co prosisz - czy możesz wyjaśnić dalej. Funkcja promisified (nieco z definicji) zwraca obietnicę i nie używa już wywołania zwrotnego. Jeśli ją wywołasz, zwróci obietnicę, która zostanie rozwiązana tylko wtedy, gdy wykonana zostanie podstawowa operacja asynchroniczna. Ponadto, jeśli chcesz operację szeregową, a nie operację równoległą, musisz przekazać odwołanie do funkcji do obsługi '.then()', a nie do zwracanej wartości wykonywania niektórych funkcji (chyba że ta wartość zwracana sama w sobie jest funkcją, która jest funkcją chcesz zadzwonić w sekwencji). – jfriend00

+2

Niestety, edycja nie pomogła w zrozumieniu problemu.Istnieje wiele różnych pomieszanych opisów problemów. Widzę co najmniej 1. test jednostkowy nie jest poprawnie zbudowany i skutkuje przekroczeniem limitu czasu (a nie błędnym stwierdzeniem) 2. nieporozumieniem, jak działa "bind". w rzeczywistości na przykład ostatni 'then' handler' _.bind (fsExtra.linkAsync ... 'otrzymuje wynik poprzedniej obietnicy w łańcuchu, ale' fsExtra.linkAsync' pobiera ją jako trzeci parametr (po 'dstPath') –

+2

Może po prostu zarchiwizujesz i opisz konkretną sekwencję operacji, które chcesz skryptować, a następnie zapytasz, jak najskuteczniejszy sposób to napisać? Wygląda na to, że próbujesz zadać jakieś ogólne pytanie, ale używając naprawdę skomplikowany zestaw kodu z mnóstwem problemów, aby spróbować opisać problem i tracisz nas w tej złożoności.Czy możesz zredukować problem do znacznie prostszego przykładu? – jfriend00

Odpowiedz

0

Oto przykład użycia 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

Oto trzy funkcje, które pozwalają jedynie na pisanie głębokich obietnic w jednym procesie. Używają modułu dotty do edycji zestawu obiektów do globalnego zasięgu poza łańcuchem obietnicy.

Z można zignorować bieżącą wartość w łańcuchu.

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

Za pomocą exportResult można zapisać wartość do późniejszego wykorzystania.

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

Z provideResult można powrócić do poprzedniej wartości wynik powrotu do następnego then rozmowy.

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

W wyniku czegoś takiego.

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... 
} 

Pierwotnie szukałem bardziej eleganckiego rozwiązania tego. Lub zalecenia dotyczące obecnego standardu, który to robi.

+0

Być może zechcesz rzucić okiem na [Ramda] (http://ramdajs.com/) – Bergi

+0

Zauważyłem, że Ramda jest naprawdę interesująca, ale nie tak pomocna, jeśli chodzi o pomoc w tworzeniu złożonych łańcuchów obietnic i zarządzaniu przepływem sterowania. – ThomasReggi

+0

Czy obejrzałeś 'curryN',' composeP'? Również twoje 'wrap' może być wykonane przy użyciu' nAry'. – Bergi