UNPKG

promisify

Version:

Convert callback-based APIs to promises

184 lines (160 loc) 5.1 kB
'use strict'; var when = require('when'); function merge_objects(a, b) { for (var prop in b) { if (!a.hasOwnProperty(prop)) { a[prop] = b[prop]; } } return a; }; // A PStream represents an incoming stream of items. Conceptually, it // is a promise accompanied by a lazy sequence of items. The promise // indicates when the sequence ends in the case of a successful // resolution, or otherwise an error from the source of the steam. // Unlike node's EventEmitter-based read streams, an error strictly // terminates the stream. function PStream() { } PStream.isPStream = function (obj) { return obj instanceof PStream; } PStream.prototype.then = function (callback, errback) { return this.sink().then(callback, errback); }; // Make a new stream by applying a function to each item in the stream PStream.prototype.map = function(f) { var underlying = this; var res = new PStream(); res.to = function (dest) { underlying.to(merge_objects({ item: function (val) { dest.item(f(val)); } }, dest)); }; return res; }; // Pass each item to a function, and reutn a promise representing the // end of the stream. PStream.prototype.sink = function (f) { var d = when.defer(); var res = d.promise; this.to({ item: function (val) { if (f) { f(val); } }, end: function () { if (d) { d.resolve(); d = null; } }, error: function (err) { if (d) { d.reject(err); d = null; } } }); return res; }; // A general pstream -> pstream transformation. Like map, but the // function is passed a callback to allow it to yield zero, one, or // many values. There is also an "end function" which makes it // possible to yield extra items downstream when the upstream ends. PStream.prototype.transform = function(funcs) { var underlying = this; var res = new PStream(); res.to = function (dest) { if (funcs.initial) { var done = false; funcs.initial(function (out) { if (!done) { dest.item(out); } }); done = true; } var new_dest = {}; if (funcs.item) { new_dest.item = function (val) { var done = false; funcs.item(val, function (out) { if (!done) { dest.item(out); } }); done = true; } } if (funcs.end) { new_dest.end = function () { var done = false; funcs.end(function (out) { if (!done) { dest.item(out); } }); done = true; dest.end(); }; } underlying.to(merge_objects(new_dest, dest)); }; return res; }; // Make a PStream that is guaranteed to be well behaved (reflects // exceptions up through "error", only calls "end" or "error" once). PStream.prototype.sanitize = function() { var underlying = this; var res = new PStream(); res.to = function (dest) { var done = false; underlying.to({ item: function (val) { if (!done) { try { dest.item(val); } catch (err) { dest.error(err); done = true; } } }, end: function () { if (!done) { try { dest.end(); } catch (err) { dest.error(err); } done = true; } }, error: function (err) { if (!done) { try { dest.error(err); } catch (err) { dest.error(err); } done = true; } } }); }; return res; }; PStream.wrap_read_stream = function (s) { var res = new PStream(); res.to = function (dest) { s.on('data', function (data) { dest.item(data); }); s.on('end', function () { dest.end(); }); s.on('error', function (err) { dest.error(err); }); // Some streams emit 'close' but not 'end', despite what the // node docs suggest. s.on('close', function () { dest.end(); }); }; return res; } // Make a PStream that yields the items in a promised array PStream.from_array = function (p) { var res = new PStream(); res.to = function (dest) { when(p, function (arr) { arr.forEach(function (item) { dest.item(item); }); dest.end(); }).then(null, function (err) { dest.error(err); }); }; return res; } // Convert a PStream to a promised array PStream.prototype.to_array = function () { var res = []; return this.map(function (item) { res.push(item); }).then(function () { return res; }); }; module.exports = PStream;