Security News
ESLint is Now Language-Agnostic: Linting JSON, Markdown, and Beyond
ESLint has added JSON and Markdown linting support with new officially-supported plugins, expanding its versatility beyond JavaScript.
The 'deferred' npm package provides a way to handle asynchronous operations in JavaScript using promises. It allows you to create deferred objects that can be resolved or rejected at a later time, making it easier to manage complex asynchronous workflows.
Creating a Deferred Object
This feature allows you to create a deferred object and resolve it at a later time. The deferred object has a promise property that can be used to handle the result of the asynchronous operation.
const deferred = require('deferred');
const d = deferred();
// Simulate an asynchronous operation
setTimeout(() => {
d.resolve('Operation successful');
}, 1000);
// Using the promise
const promise = d.promise;
promise.then((result) => {
console.log(result); // Output: Operation successful
});
Chaining Promises
This feature demonstrates how to chain multiple promises together. Each then() method returns a new promise, allowing you to handle the result of one asynchronous operation and pass it to the next.
const deferred = require('deferred');
const d = deferred();
// Simulate an asynchronous operation
setTimeout(() => {
d.resolve('First operation successful');
}, 1000);
// Chaining promises
const promise = d.promise;
promise
.then((result) => {
console.log(result); // Output: First operation successful
return 'Second operation successful';
})
.then((result) => {
console.log(result); // Output: Second operation successful
});
Handling Errors
This feature shows how to handle errors in asynchronous operations using the catch() method. If the deferred object is rejected, the catch() method will be called with the error.
const deferred = require('deferred');
const d = deferred();
// Simulate an asynchronous operation
setTimeout(() => {
d.reject(new Error('Operation failed'));
}, 1000);
// Using the promise
const promise = d.promise;
promise
.then((result) => {
console.log(result);
})
.catch((error) => {
console.error(error.message); // Output: Operation failed
});
Bluebird is a fully-featured promise library for JavaScript. It provides a wide range of features for working with promises, including advanced error handling, promise cancellation, and more. Compared to 'deferred', Bluebird offers more extensive functionality and better performance.
Q is a promise library that allows you to create and manage promises in JavaScript. It provides a similar API to 'deferred' but includes additional features such as progress notifications and more advanced promise chaining. Q is a good alternative if you need more control over your asynchronous operations.
When is a lightweight promise library that focuses on performance and ease of use. It provides a simple API for creating and managing promises, similar to 'deferred'. When is a good choice if you need a minimalistic promise library with good performance.
Promises simple, straightforward and powerful way. It was build with less is more mantra in mind, API consist of just 7 functions which should give all you need to configure complicated asynchronous control flow.
This work is highly inspired by other deferred/promise implementations, in particular Q by Kris Kowal.
It's plain EcmaScript, but out of the box currently works only with node & npm (due to it's CommonJS package):
$ npm install deferred
For browser or other environments it needs to be bundled with few dependencies from es5-ext project (code states specifically which). Browser ready files will be available in near future.
## Deferred/Promise concept ### BasicsWhen there's work to do that doesn't return immediately (asynchronous), deferred
object is created and promise (deferred.promise
) is returned to the world. When finally value is obtained, deferred is resolved with it deferred.resolve(value)
. At that point all promise observers (added in meantime via deferred.promise.then
) are notified with value of fulfilled promise.
Example:
var deferred = require('deferred');
var later = function () {
var d = deferred();
setTimeout(function () {
d.resolve(1);
}, 1000);
return d.promise;
};
later().then(function (n) {
console.log(n); // 1
});
promise
is really a then
function, so you may use it directly:
later()
(function (n) {
console.log(n); // 1
});
promise
takes callback and returns another promise. Returned promise will resolve with value that is a result of callback function, this way, promises can be chained:
later()
(function (n) {
var d = deferred();
setTimeout(function () {
d.resolve(n + 1);
}, 1000);
return d.promise;
})
(function (n) {
console.log(n); // 2
});
Callback passed to promise
may return anything, it may also be regular synchronous function:
later()
(function (n) {
return n + 1;
})
(function (n) {
console.log(n); // 2
});
Promises can be nested. If promise resolves with another promise, it's not really resolved. It's resolved only when final promise returns real value:
var count = 0;
var laterNested = function fn (value) {
var d = deferred();
setTimeout(function () {
value *= 2;
d.resolve((++count === 3) ? value : fn(value));
}, 1000);
return d.promise;
};
laterNested(1)(function (n) {
console.log(n); // 8
});
Promise can be resolved only once, and callbacks passed to promise
are also called only once, no exceptions. For deeper insight into this concept, and to better understand design decisions please see Kris Kowal design notes, it's well worth read.
Promise is rejected when it's resolved with an error, same way if callback passed to promise
throws exception it becomes resolution of promise returned by promise
call. To handle error, pass second callback to promise
:
later()
(function (n) {
throw new Error('error!')
})
(function () {
// never called
}, function (e) {
// handle error;
});
When there is no error callback passed, error is silent. To expose error, end chain with .end()
, then error that broke the chain will be thrown:
later()
(function (n) {
throw new Error('error!')
})
(function (n) {
// never executed
})
.end(); // throws error!
end
takes optional handler so instead of throwing, error can be handled other way. Behavior is exactly same as when passing second callback to promise
:
later()
(function (n) {
throw new Error('error!')
})
.end(function (e) {
// handle error!
});
## Asynchronous functions as promises
There is a known convention in JavaScript for working with asynchronous calls. Following approach is widely used within node.js:
var afunc = function (x, y, callback) {
setTimeout(function () {
try {
callback(null, x + y);
} catch (e) {
callback(e);
}
}, 1000);
};
Asynchronous function receives callback argument, callback handles both error and success. There's easy way to turn such functions into promises and take advantage of promise design. There's deferred.asyncToPromise
for that, let's use shorter name:
var a2p = deferred.asyncToPromise;
// we can also import it individually:
a2p = require('deferred/lib/async-to-promise');
This method can be used in various ways.
First way is to assign it directly to asynchronous method:
afunc.a2p = a2p;
afunc.a2p(3, 4)
(function (n) {
console.log(n); // 7
});
Second way is more traditional (I personally favor this one as it doesn't touch asynchronous function):
a2p = a2p.call;
a2p(afunc, 3, 4)
(function (n) {
console.log(n); // 7
});
Third way is to bind method for later execution. We'll use ba2p
name for that:
var ba2p = require('deferred/lib/async-to-promise').bind;
var abinded = ba2p(afunc, 3, 4);
// somewhere in other context:
abinded()
(function (n) {
console.log(n); // 7
});
Note that this way of using it is not perfectly safe. We need to be sure that abinded
will be called without any not expected arguments, if it's the case, then it won't execute as expected:
abinded(7, 4); // TypeError: number is not a function.
Node.js example, reading file, changing it's content and writing under different name:
var fs = require('fs');
a2p(fs.readFile, __filename, 'utf-8')
(function (content) {
// change content
return content;
})
(ba2p(fs.writeFile, __filename + '.changed'))
.end();
## Control-flow, joining promises
There are three dedicated methods for joining promises. They're avaiable on deferred
as deferred.join
, deferred.all
and deferred.first
. Let's access them directly:
// let's access them directly:
var join = deferred.join;
var all = deferred.all;
var first = deferred.first;
As with other API methods, they can also be imported individually:
var join = require('deferred/lib/join/default')
, all = require('deferred/lib/join/all')
, first = require('deferred/lib/join/first');
Join methods take arguments of any type and internally distinguish between promises, functions and others. Call them with list of arguments or an array:
join(p1, p2, p3);
join([p1, p2, p3]); // same behavior
join
and all
return another promise, which resolves with combined result of resolved arguments:
join(p1, p2, p3)
(function (result) {
// result is array of resolved values of p1, p2 and p3.
});
first
results with value of first resolved argument:
first(p1, p2, p3)
(function (result) {
// result is resolved p1, p2 or p3, whichever was first
});
### join(...)
join
returns promise which resolves with an array of resolved values of all arguments.
Values may be anything, also errors (rejected promises, functions that thrown errors, errors itself). Returned promise always fulfills, never rejects.
Same as join
, with that difference that all arguments need to be succesful.
If there's any error, join execution is stopped (following functions are not called), and promise is rejected with error that broke the join chain. In succesful case returned promise value is same as in join
.
Fulfills with first succesfully resolved argument. If all arguments fail, then promise rejects with error that occurred last.
### Non promise argumentsAs mentioned above, join functions take any arguments, not only promises. Function arguments are called with fully resolved previous argument, if one resolved succesfully. If previous argument failed then function is never called. Error that rejected previous argument becomes also result of following function within returned result array. Any other values (neither promises or functions) are treated as if they were values of resolved promises.
### Examples:Previous read/write file example written with all
:
all(
a2p(fs.readFile, __filename, 'utf-8'),
function (content) {
// change content
return content;
},
ba2p(fs.writeFile, __filename + '.changed')
).end();
Concat all JavaScript files in given directory and save it to lib.js:
all(
// Read all filenames in given path
a2p(fs.readdir, __dirname),
// Filter *.js files
function (files) {
return files.filter(function (name) {
return (name.slice(-3) === '.js');
});
},
// Read files content
function (files) {
return join(files.map(function (name) {
return a2p(fs.readFile, name, 'utf-8');
}));
},
// Concat into one string
function (data) {
return data.join("\n");
},
// Write to lib.js
ba2p(fs.writeFile, __dirname + '/lib.js')
).end();
We can shorten it a bit with introduction of functional sugar, it's out of scope of this library but I guess worth an example:
var invoke = require('es5-ext/lib/Function/invoke');
all(
// Read all filenames in given path
a2p(fs.readdir, __dirname),
// Filter *.js files
invoke('filter', function (name) {
return (name.slice(-3) === '.js');
}),
// Read files content
invoke('map', function (name) {
return a2p(fs.readFile, name, 'utf-8');
}), join,
// Concat into one string
invoke('join', "\n"),
// Write to lib.js
ba2p(fs.writeFile, __dirname + '/lib.js')
).end();
invoke
implementation can be found in es5-ext
project: https://github.com/medikoo/es5-ext/blob/master/lib/Function/invoke.js
Let's say we're after content that is paginated over many pages on some website (like search results). We don't know how many pages it spans. We only know by reading page n whether page n + 1 exists.
First things first. Simple download function, it downloads page at given path from predefinied domain and returns promise:
var http = require('http');
var getPage = function (path) {
var d = deferred();
http.get({
host: 'www.example.com',
path: path
}, function(res) {
res.setEncoding('utf-8');
var content = "";
res.on('data', function (data) {
content += data;
});
res.on('end', function () {
d.resolve(content);
});
}).on('error', d.resolve);
return d.promise;
};
Deferred loop:
var n = 1, result;
getPage('/page/' + n++)
(function process (content) {
// populate result
// decide whether we need to download next page
if (isNextPage) {
return getPage('/page/' + n++)(process);
} else {
return result;
}
})
(function (result) {
// play with final result
}).end();
We can also make it with all
:
var n = 1, result;
all(
getPage('/page/' + n++),
function process (content) {
// populate result
// decide whether we need to download next page
if (isNextPage) {
return getPage('/page/' + n++)(process);
} else {
return result;
}
},
function (result) {
// play with final result
}
).end();
### Comparision to other solutions that take non promise approach
Following are examples from documentation of other solutions rewritten deferred/promise way. You'll be the judge, which solution you find more powerful and friendly.
#### Step -> https://github.com/creationix/stepFirst example from Step README, using chained promises:
a2p(fs.readFile, __filename, 'utf-8')
(function capitalize (txt) {
return txt.toUpperCase();
})
(function showIt (newTxt) {
console.log(newTxt);
})
.end();
Again we can make it even more concise with functional sugar:
a2p(fs.readFile, __filename, 'utf-8')
(invoke('toUpperCase'))
(console.log)
.end();
#### Async -> https://github.com/caolan/async
##### async.series:
all(
function () {
// do some stuff ...
return 'one';
},
function () {
// do some more stuff
return 'two';
}
)
(function (results) {
// results is now equal to ['one', 'two']
},
function (err) {
// handle err
});
##### async.paralles:
For parallel execution we pass already initialized promises:
all(
promise1,
a2p(asyncFunc, arg1, arg2),
promise2
)
(function (results) {
// results are resolved values of promise1, asyncFunc and promise2
},
function (err) {
// handle err
});
##### async.waterfall:
Resolved values are always passed to following functions, so again we have it out of a box:
all(
function () {
return ['one', 'two'];
},
function (args) {
return 'three';
},
function (arg1) {
// arg1 now equals 'three'
}
);
##### async.auto
It's a question of combining all
joins. First example from docs:
all(
all(
a2p(get_data),
a2p(make_folder)
),
ba2p(write_file)
ba2p(email_link)
).end();
##### async.whilst, async.until
See Asynchronous loop example, it shows how easy is to configure loops.
##### async.forEach, async.map, async.filter ..etc.Asynchronous handlers for array iterators, forEach and map:
all(arr, function (item) {
// logic
return promise;
})
(function (results) {
// deal with results
// if it's forEach than results are obsolete
})
.end();
I decided not to implement array iterator functions in this library, for two reasons, first is as you see above - it's very easy and straightforward to setup them with provided join methods, second it's unlikely we need most of them.
FAQs
Modular and fast Promises implementation
The npm package deferred receives a total of 1,006,029 weekly downloads. As such, deferred popularity was classified as popular.
We found that deferred demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
ESLint has added JSON and Markdown linting support with new officially-supported plugins, expanding its versatility beyond JavaScript.
Security News
Members Hub is conducting large-scale campaigns to artificially boost Discord server metrics, undermining community trust and platform integrity.
Security News
NIST has failed to meet its self-imposed deadline of clearing the NVD's backlog by the end of the fiscal year. Meanwhile, CVE's awaiting analysis have increased by 33% since June.