The answer to the question is divided into two parts: (a) processing of asynchronous functions inside the Meteor methods and (b) using the youtube-dl package.
Async functions inside Meteor methods
In Meteor methods, there are 2+ ways to work with asynchronous functions: using future and using wrapAsync . If you look at the sources of Meteor, you will see that wrapAsync itself uses the future : https://github.com/meteor/meteor/blob/master/packages/meteor/helpers.js#L90 . You can also use fibers directly, but not recommended .
The following are common examples of their use:
'use strict'; if (Meteor.isClient) { Template.methods.events({ 'click #btnAsync' : function() { console.log('Meteor.call(asyncMethod)'); Meteor.call('asyncMethod', 1000, function(error, result) { if (error) { console.log('Meteor.call(asyncMethod): error:', error); } else { console.log('Meteor.call(asyncMethod): result:', result); } }); }, 'click #btnFuture' : function() { console.log('Meteor.call(futureMethod)'); Meteor.call('futureMethod', 1000, function(error, result) { if (error) { console.log('Meteor.call(futureMethod): error:', error); } else { console.log('Meteor.call(futureMethod): result:', result); } }); }, 'click #btnFiber' : function() { console.log('Meteor.call(fiberMethod)'); Meteor.call('fiberMethod', 1000, function(error, result) { if (error) { console.log('Meteor.call(fiberMethod): error:', error); } else { console.log('Meteor.call(fiberMethod): result:', result); } }); } }); } if (Meteor.isServer) { var demoFunction = function(duration, callback) { console.log('asyncDemoFunction: enter.'); setTimeout(function() { console.log('asyncDemoFunction: finish.'); callback(null, { result: 'this is result' }); }, duration); console.log('asyncDemoFunction: exit.'); }; var asyncDemoFunction = Meteor.wrapAsync(demoFunction); var futureDemoFunction = function(duration) { var Future = Npm.require('fibers/future'); var future = new Future(); demoFunction(duration, function(error, result) { if (error) { future.throw(error); } else { future.return(result); } }); return future.wait(); }; var fiberDemoFunction = function(duration) { var Fiber = Npm.require('fibers'); var fiber = Fiber.current; demoFunction(duration, function(error, result) { if (error) { fiber.throwInto(new Meteor.Error(error)); } else { fiber.run(result); } }); return Fiber.yield(); }; Meteor.methods({ asyncMethod: function (duration) { return asyncDemoFunction(duration); }, futureMethod: function (duration) { return futureDemoFunction(duration); }, fiberMethod: function (duration) { return fiberDemoFunction(duration); } }); }
You can also look at Meteor.bindEnvironment() and future.resolver() for more complex cases.
Christian Fritz provided the correct template for using wrapAsync , however, within 2 years, from the moment the initial question was asked, the youtube-dl package API has changed.
Using the youtube-dl package
Due to changes in the API, if you run its code, the server throws an exception visible on its console:
Exception while invoking method 'download' TypeError: Object function (videoUrl, args, options) { ... } has no method 'download'
And Meteor returns an undefined value to the client:
here is the path: undefined
Below is the code (just replace downloadDir with your path) and return the file name to the client:
here is the path: test.mp4
index.html file
<head> <title>meteor-methods</title> </head> <body> {{> hello}} </body> <template name="hello"> <form> <input type="text" id="youtube-url" value="https://www.youtube.com/watch?v=alIq_wG9FNk"> <input type="button" id="downloadBtn" value="Download by click"> <input type="submit" value="Download by submit"> </form> </template>
index.js file:
'use strict'; if (Meteor.isClient) { //Path = new Meteor.Collection("path"); //Meteor.subscribe("path"); Template.hello.events( { 'submit .form, click #downloadBtn' : function() { var link = document.getElementById("youtube-url").value; //Meteor.call('download', link); Meteor.call('download', link, function(err, path) { if (err) { console.log('Error:', err); } else { console.log("here is the path:", path); } }); event.preventDefault(); } } ); } if (Meteor.isServer) { var fs = Npm.require('fs'); var youtubedl = Npm.require('youtube-dl'); var downloadSync = Meteor.wrapAsync(function(link, callback) { var fname = 'test.mp4'; // by default it will be downloaded to // <project-root>/.meteor/local/build/programs/server/ var downloadDir = './'; console.log('\nStarting download...'); // var dl = youtubedl.download(link, './videos'); var dl = youtubedl(link, [], []); dl.on('info', function(info) { console.log('\nDownload started: ' + info._filename); }); // dl.on('end', function(data) { dl.on('end', function() { console.log('\nDownload finished!'); //callback(null, './videos/' + data.filename); callback(null, fname); }); dl.on('error', function(error) { console.log('\nDownload error:', error); callback(new Meteor.Error(error.message) ); }); dl.pipe(fs.createWriteStream(downloadDir + fname)); }); Meteor.methods({ download: function (link) { return downloadSync(link); } }); }
Current API does not allow youtube file name to be received when saving a file. If you want to save the file with the youtube file name (as indicated in the initial question), you need to use the getInfo() method of the youtube-dl package.