I am very new to Javascript and Node.js in general . As such i am trying to get my head around the concept of asynchronous programming in Node.js and also how to use the same in AWS lambda.
I came across this blog post on Amazon blogs which explains the support for async/await in Node.js lambda functions : https://aws.amazon.com/blogs/compute/node-js-8-10-runtime-now-available-in-aws-lambda/
So far as i knew the concept of a function generating a promise is as in the below code snippet:
const func1 = () => {
console.log("Starting with execution of func1...")
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("First promise ended after 3 secs")
}, 3000)
})
}
So in here the function explicitly generates a Promise and returns the same.
But in the above AWS blog post i see a function definition as such :
let AWS = require('aws-sdk');
let lambda = new AWS.Lambda();
exports.handler = async (event) => {
return await lambda.getAccountSettings().promise() ;
};
Now i have checked the AWS SDK for Node.js documentation (https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Lambda.html#getAccountSettings-property) and i see that the function getAccountSettings accepts a callback as the third parameter.
I am confused with the .promise() syntax of generating promises .How does a function ensure that using that syntax it would give back a promise object . Because from the docs there is no mention that if i use .promise() it would return a promise instead. I am assuming there might be a rule of thumb here in this aspect.
Also instead of return await lambda.getAccountSettings().promise() if i just write return await lambda.getAccountSettings() what difference would it make .
Is there any documentation on this that i can refer to ?
Request you to please throw some more light on this new way of getting promise object back.
Thanks for any help in advance.
If you would like to understand why the .promise() method was made available, and why the promise was not just returned just-like-that, note how such an API evolves over time, and backwards compatibility must be maintained.
Let's build something similar, but much simplified. Let's make a function that provides 1/x for a given number, but an error object when x=0. This will be done asynchronously.
Also, we want the function to return synchronously an object that allows one to register a listener for when an error occurs, another for when there is success, and yet another when either of the two happens. This is a much simplified idea from what AWS returns: there you get a very rich Request object.
So imagine we are in 2012 and promises are not yet widely available/used in JavaScript. So we provide the following API for our asynchronous 1/x function:
// Implementation in the first version of the API (without promises):
// * returns an object with which you can register a listener
// * accepts an optional callback
function getAsyncInverse(num, callback) {
var onSuccess = [], // callbacks that are called on success
onError = [], // callbacks that are called on failure
onComplete = []; // callbacks that are called in both situations
if (callback) onComplete.push(callback);
function complete(err=null) {
var result = null;
if (num === 0) err = new Error("Division by Zero");
else result = 1/num;
// Communicate the result/error to the appropriate listeners:
if (err) for (var i = 0; i < onError.length; i++) onError[i](err);
else for (var i = 0; i < onSuccess.length; i++) onSuccess[i](result);
for (var i = 0; i < onComplete.length; i++) onComplete[i](err, result);
}
var timeoutId = setTimeout(complete, 100);
var request = {
on: function (type, callback) {
if (type === "success") onSuccess.push(callback);
else if (type === "error") onError.push(callback);
else if (type === "complete") onComplete.push(callback);
return request;
},
abort: function () {
clearTimeout(timeoutId);
complete(new Error("aborted"));
return request;
}
}
return request;
}
// How version 1 is used, by registering a listener via the returned object
var num = 2;
var request = getAsyncInverse(num); // let's not pass a callback here
request.on("success", function (result) { // ... but use the request object
console.log("The result is:", result);
}).on("error", function (err) {
console.log("There was an error:", err);
});
But then promises become more popular and users of your API are pushing for a promise API. You want to ensure backwards compatibility, and so decide to just extend the returned request-object with one additional property, a method: promise()
Here is how the above implementation would be altered to make that happen:
// Implementation in the second version of the API (with promise), but backwards-compatible
// * returns an object with which you can register a listener, or get the promise object
// * accepts an optional callback
function getAsyncInverse(num, callback) {
let onSuccess = [], // callbacks that are called on success
onError = [], // callbacks that are called on failure
onComplete = []; // callbacks that are called in both situations
if (callback) onComplete.push(callback);
let request;
// New: create a promise, and put the logic inside the promise-constructor callback
let promise = new Promise(function (resolve, reject) {
function complete(err=null) {
let result = null;
if (num === 0) err = new Error("Division by Zero");
else result = 1/num;
// Communicate the result/error to the appropriate listeners:
if (err) for (let callback of onError) callback(err);
else for (let callback of onSuccess) callback(result);
for (let callback of onComplete) callback(err, result);
// New: also call resolve/reject
if (err) reject(err);
else resolve(result);
}
let timeoutId = setTimeout(complete, 100);
request = {
on: function (type, callback) {
if (type === "success") onSuccess.push(callback);
else if (type === "error") onError.push(callback);
else if (type === "complete") onComplete.push(callback);
return request;
},
abort: function () {
clearTimeout(timeoutId);
complete(new Error("aborted"));
return request;
},
promise: function () { // <--- added feature!
return promise;
}
};
});
return request; // We return the same as in version-1, but with additional promise method
}
// How version 2 is used, by getting the new promise method
let num = 2;
let promise = getAsyncInverse(num).promise();
promise.then(function (result) {
console.log("The result is:", result);
}).catch(function (err) {
console.log("There was an error:", err);
});
As you can see, it would not be a good idea to omit the request object, and have the function return the promise. This would break existing code using your API (no backwards compatibility).
Related
I'm developing a Google Chrome extension which collects data from two servers and sends it to another service. I don't understand how to make it asynchronous. The requests seem to work fine.
I searched Google for some explanations but just found some basic tutorials with timeouts. Also, the Product-Server accepts the Ajax request, the Deal-Server doesn't (CORS Error). So I used XMLHttpRequest.
document.addEventListener("DOMContentLoaded", function () {
var createButton = document.getElementById("createButton");
createButton.addEventListener("click", function () {
getProducts();
getDeals();
}, false)
function getProducts() {
var list = [];
chrome.tabs.getSelected(null, function (tab) {
var Url = parseDealIdToUrl(tab.url)
$.get(Url, function (data, status) {
const jsonData = JSON.parse(JSON.stringify(data))
const productArray = jsonData.data
productArray.forEach(product => {
productList.push(new Product(product.id, product.deal_id, product.product_id, product.name, product.item_price, product.quantity, product.duration, product.discount_percentage, product.currency, product.sum, product.sum_formatted))
});
})
});
}
function getDeals(maxPages = 1, currentPage = 1, akquises = []) {
var akquiseList = akquises;
if (currentPage <= maxPages) {
var Url = dealUrl + currentPage
var xhr = new XMLHttpRequest();
xhr.open("GET", Url, true);
xhr.setRequestHeader("Authorization", "Token token=")
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
const akquiseArray = JSON.parse(xhr.responseText);
akquiseArray.forEach(akquise => {
akquiseList.push(new Akquise(akquise.name, akquise.id))
});
getDeals(handlePagination(xhr.getResponseHeader("Link")), currentPage + 1, akquiseList)
}
}
xhr.send();
}
}
}, false)
I want to call both functions and wait till both Lists are filled, then send the Data to the Service. Any idea would help me!
I'm not quite sure what you mean by "make it asynchronous." As wOxxOm said, XMLHttpRequest is an asynchronous method. Do you mean you're not sure how to combine the results of multiple asynchronous operations? For the sake of this answer I'll assume that's the case.
Basic Asynchronicity
In order to break down how asynchronous functions work, let's look at a simplified example of your code. Below we have a main function that calls 2 different asynchronous functions. When you run this block you'll get a DONE message logged to the console, Async 1 complete logged after 1 second, and Async 2 complete logged 1 more second later.
// Copyright 2019 Google LLC.
// SPDX-License-Identifier: Apache-2.0
(function main() {
doAsync1();
doAsync2();
console.log('DONE');
})()
function doAsync1() {
setTimeout(() => {
console.log('Async 1 complete');
}, 1000);
}
function doAsync2() {
setTimeout(() => {
console.log('Async 2 complete');
}, 2000)
}
The reason DONE is logged before the other statements is because doAsync1 and doAsync2 are asynchronous – it take a couple seconds for them to complete their work. When you call doAsync1() in main, the JS engine will step into the doAsync1 function and start executing lines. The first line is a setTimeout call. This function takes its first argument and schedules it for execution 1000 milliseconds later.
At this point the JS engine has done everything it can in doAsync1, so it steps out of that function and calls the next line, doAsync2. Again, doAsync2 schedules its callback for future execution and returns.
Next, the engine will execute the console.log line which makes DONE appear in the console.
1000 ms later, the callback scheduled by doAsync1 will run execute and log Async 1 complete to the console. Another 1000 ms later the callback scheduled by doAsync2 will log Async 2 complete.
Basic Callbacks
Now let's say doAsync1 and doAsync2 generate some data we want to use in main once both of complete. In JS, we traditionally use callbacks to get notified when some operation we're interested in completes.
// Copyright 2019 Google LLC.
// SPDX-License-Identifier: Apache-2.0
function doAsync1(callback) {
setTimeout(() => {
console.log('Async 1 started');
const data = "Async 1 payload";
callback(data);
}, 1000);
}
function doAsync2(callback) {
setTimeout(() => {
console.log('Async 2 started');
const data = "Async 2 payload";
callback(data);
}, 2000);
}
(function main() {
const response = {};
doAsync1(handleAsync1);
doAsync2(handleAsync2);
function handleAsync1(data) {
response.async1 = data;
handleComplete();
}
function handleAsync2(data) {
response.async2 = data;
handleComplete();
}
function handleComplete() {
if (response.hasOwnProperty('async1') && response.hasOwnProperty('async2')) {
console.log('DONE', response);
}
}
})();
Promises
While this does the job, it's a bit verbose. Promises are an abstraction of one-time callbacks that makes it easier to chain blocks of work together.
// Copyright 2019 Google LLC.
// SPDX-License-Identifier: Apache-2.0
// Promisified version of setTimeout
function timeout(duration) {
return new Promise(resolve => {
setTimeout(resolve, duration);
});
}
function doAsync1(callback) {
return timeout(1000).then(() => {
console.log('Async 1 started');
const data = "Async 1 payload";
return data;
});
}
function doAsync2(callback) {
return timeout(2000).then(() => {
console.log('Async 2 started');
const data = "Async 2 payload";
return data;
});
}
(function main() {
// Starts both doAsync1 and doAsync2 at the same time. Once both complete, the
// promise will resolve with both response values.
Promise.all([
doAsync1(),
doAsync2()
]).then(response => {
console.log('DONE', response[0], response[1]);
});
})();
Async/Await
With ES2016 we gained 2 new keywords: async and await. These keywords are essentially syntactic sugar that make it a little easier to work with promises in JavaScript. For demo purposes, let's take a look at our Promises example converted to async/await.
// Copyright 2019 Google LLC.
// SPDX-License-Identifier: Apache-2.0
function timeout(duration) {
return new Promise(resolve => {
setTimeout(resolve, duration);
});
}
async function doAsync1(callback) {
await timeout(1000);
console.log('Async 1 started');
const data = "Async 1 payload";
return data;
}
async function doAsync1(callback) {
await timeout(2000);
console.log('Async 2 started');
const data = "Async 2 payload";
return data;
}
(async function main() {
const response = await Promise.all([
doAsync1(),
doAsync2()
]);
console.log('DONE', response[0], response[1]);
})();
For a much deeper dive into async functions, check out Async functions - making promises friendly by Jake Archibald.
Use the following code snippet to add async/await functions to chrome extension.
Usage: put the following code snippet to the beginning of both your content script and background script.
/**
* Usage:
* let cookies = await asyncfy(chrome.cookies.getAll)({ url })
* let tabs = await asyncfy(chrome.tabs.query)({active: true, currentWindow: true})
*
* #param fn A function that takes one or more parameters, and the last parameter is a callback which has one or more parameter. The simplest one is chrome.management.getSelf
* #returns {function(...[*]): Promise<any>} Return one value if the results array has only one element, else return the whole results array
*/
let asyncfy = fn => (...args) => {
return new Promise((resolve, reject) => {
fn(...args, (...results) => {
let { lastError } = chrome.runtime
if (typeof lastError !== 'undefined') reject(lastError);
else results.length == 1 ? resolve(results[0]) : resolve(results);
});
});
}
let isObject = function(obj) {
var type = typeof obj;
return type === 'function' || type === 'object' && !!obj;
};
// provide async method to all methods which have one callback.
let handler = {
get: function(target, prop, receiver) {
let value = target[prop]
let type = typeof value
if(type !== 'undefined') { // including null, false
if( type === 'function') return value.bind(target); // correct the this for the functions, since we've substituted the original object to the proxy object
return value;
}
if(prop.endsWith('Async')){
let key = prop.replace(/Async$/, '')
let method=target[key]
let asyncMethod = asyncfy(method.bind(target));
return asyncMethod;
}
}
}
// proxy every leaf object
let asyncfyObj = handler => obj => Object.getOwnPropertyNames(obj)
.filter(prop => isObject(obj[prop]))
.forEach(prop => obj[prop] = new Proxy(obj[prop], handler))
// intercept the getters of all object in chrome member
asyncfyObj(handler)(chrome)
asyncfyObj(handler)(chrome.storage)
// console.log(`active tab: ${JSON.stringify(await getActiveTabAsync())}`)
let getActiveTabAsync = async () => {
let tabs = await chrome.tabs.queryAsync({active: true, currentWindow: true});
return (tabs && tabs.length > 0) ? tabs[0] : null;
}
// chrome.storage.local.set({ foo: 'bar' });
// console.log(`foo: ${await getLocalStorageAsync('foo')}`)
let getLocalStorageAsync = async key => ( await chrome.storage.local.getAsync([key]) ) [key];
Testing: put the following snippet in your background script and make sure related permissions have been added to the manifest.json.
(async () => {
console.log(cookies: ${JSON.stringify(await asyncfy(chrome.cookies.getAll)({ url: 'https://www.stackoverflow.com/' }))})
console.log(active tab: ${JSON.stringify(await getActiveTabAsync())})
chrome.storage.local.set({ 'foo': 'bar'});
console.log(storage: ${await getLocalStorageAsync('foo')})
console.log(extension install type: ${( await chrome.management.getSelfAsync() )['installType']})
} )()
my gist
Suppose you have an Array/Object that contains a list of values. Lets say those a mysql commands or urls or filespaths. Now you want to iterate over all of them and execute some code over every entry.
for(let i = 0; i < urls.length; i++){
doSthWith(urls[i]);
}
No Problem so far. But now lets say each function has a callback and needs the result of the last execution. e.g. you request something from one website and you want to use the results of this request for one of your following requests.
for(let i = 0; i < urls.length; i++){
if(resultOfLastIteration.successful){ //or some other result besides the last one
doSthWith(urls[i]);
}
}
Now lets say the length of urls (or sth similar) is over 100. Thats why you normaly use a loop so you dont need to write the same function a 100 times. That also means that Promises wont do the trick either (except Im unaware trick a trick), because you have the same problem:
doSthWith(urls[0]).then(...
doSthWith(urls[1]).then(... //either put them inside each other
).then(...
doSthWith(urls[i]) //or in sequence
...
).catch(err){...}
Either way I dont see a way to use a loop.
A way that I found but isnt really "good" is to use the package "wait.for"(https://www.npmjs.com/package/wait.for). But what makes this package tricky is to launch a fiber each time you want to use wait.for:
//somewhere you use the function in a fiber Context
wait.for(loopedExecutionOfUrls, urls);
//function declaration
function loopedExecutionOfUrls(urls, cb){
//variables:
for(let i = 0; i < urls.length; i++){
if(someTempResultVar[i-1] === true){
someTempResultVar = wait.for(doSthWith,urls[i]);
} else if(...){...}
}
}
But Im not sure if this approach is really good, besides you always have to check if you have wrapped the whole thing in a Fiber so for each function that has loops with functions that have callbacks. Thus you have 3 levels: the lauchFiber level, wait.for(loopedFunction) level and the wait.for the callback function level. (Hope I that was formulated understandable)
So my questions is: Do you guys have a good approach where you can loop throw callback functions and can use results of those whenever you like?
good = easy to use, read, performant, not recursive,...
(Im sorry if this question is stupid, but I really have problems getting along with this asynchronous programming)
If you want to wait for doSthWith to finish before doing the same but with the nex url, you have to chain your promises and you can use array.prototype.reduce to do that:
urls = ["aaa", "bbb", "ccc", "ddd"];
urls.reduce((lastPromise, url) => lastPromise.then((resultOfPreviousPromise) => {
console.log("Result of previous request: ", resultOfPreviousPromise); // <-- Result of the previous request that you can use for the next request
return doSthWith(url);
}), Promise.resolve());
function doSthWith(arg) { // Simulate the doSthWith promise
console.log("do something with: ", arg);
return new Promise(resolve => {
setTimeout(() => resolve("result of " + arg), 2000);
});
}
Use async, specifically async.each:
const async = require('async');
function doSthWith(url, cb) {
console.log('doing something with ' + url);
setTimeout(() => cb(), 2000);
}
const urls = ['https://stackoverflow.com/', 'https://phihag.de/'];
async.each(urls, doSthWith, (err) => {
if (err) {
// In practice, likely a callback or throw here
console.error(err);
} else {
console.log('done!');
}
});
Use async.map if you are interested in the result.
When I need to loop over promises I use my handy dandy ploop function. Here is an example:
// Function that returns a promise
var searchForNumber = function(number) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
var min = 1;
var max = 10;
var val = Math.floor(Math.random()*(max-min+1)+min);
console.log('Value is: ' + val.toString());
return resolve(val);
}, 1000);
});
};
// fn : function that should return a promise.
// args : the arguments that should be passed to fn.
// donefn : function that should check the result of the promise
// and return true to indicate whether ploop should stop or not.
var ploop = function(fn, args, donefn) {
return Promise.resolve(true)
.then(function() {
return(fn.apply(null, args));
})
.then(function(result) {
var finished = donefn(result);
if(finished === true){
return result;
} else {
return ploop(fn, args, donefn);
}
});
};
var searchFor = 4;
var donefn = function(result) {
return result === searchFor;
};
console.log('Searching for: ' + searchFor);
ploop(searchForNumber, [searchFor], donefn)
.then(function(val) {
console.log('Finally found! ' + val.toString());
process.exit(0);
})
.catch(function(err) {
process.exit(1);
});
Inside a promise, I need to call and process an indeterminate number of asynch API responses after individually calling them either inside another promise, or after said promise, but before another so the order of execution is respected.
var promiseA = function() {
return new Promise(function(resolve, reject) {
// 1. Establish objects needed from one API endpoint
// 2. Call API endpoint for each object and parse
// 3. Only then continue to next promise
}
}
var finalPromise = function() {
return new Promise(function(resolve, reject) {
//
}
}
promiseA()
.then(finalPromise)
So inside promiseA, I find out how many objects I'll need to poll individually from an API. Each request is of course asynchronous. I need to make these calls and process the response before the final promise is called.
I am struggling to determine a pattern for this with promises, where I can dynamically create these promises and only allow the final promise to execute after the indeterminate and asynchronous have executed and processed. I've worked with other languages where this is possible, but I'm struggling to see it here with Promises.
Any help is appreciated.
I have changed the answer to incorporate the comments below. Since, you mentioned ES6 promises I shall stick to that. There are two basic types of callbacks that we might care about.
DOM load or other one time event callbacks (window.onload and so on)
Async method callback (AJAX call, setTimout and so on)
Since,
1.DOM load or other one time event
var p = new Promise(function(res, rej) {
window.onload = res();
};
2.Plain callback: these are callbacks that don't conform to a convention. e.g. setTimeout
var p = new Promise(function(res, rej){
setTimeout(function() {
//your business/view logic
success? res():rej(); //if successful resolve else reject
}, 2000);
});
In each of the above case the promise (var p) can be wrapped to be returned by a function.
var myAsyncMethod = function () {
var p = new ... // as mentioned in 1 or 2
return p;
}
Then the usage:
myAsyncMethod()
.then(function(){/* success-handler */})
.catch(function(/* failure-handler */));
Specific to your question you may have many such methods:
function baseAJAXCall (url) {
new Promise(functoin(rej, res) {
$.get(url, function(err, data){
if(err) {
rej();
}
else {
resolve(data);
}
});
}
};
function callAPIEndpoint(url) {
return baseAJAXCall(url);
}
function finalPromiseHandler () {
//your final business/view logic
}
//USAGE
callAPIEndpoint('/my-first-call')
.then(function(data){
var promiseArray = data.map(function(item){
return baseAJAXCall(item.url);
});
return Promise.all(promiseArray);
})
.then(finalPromiseHandler)
.catch(function(){
console.log('.error-message.');
});
Ref:
How do I convert an existing callback API to promises?.
http://www.datchley.name/es6-promises/
Links from comments below.
---OLD ANSWER: PLEASE OVERLOOK---
I am familiar with this library : https://github.com/kriskowal/q. And, you can do this using using the q.all and q.allSettled constructs. May be that is what you are looking for.
Normally, the pattern is to create a function that returns a promise.
function someAsyncFuncName1(url) {
var def = q.defer();
//async function
$.get(url, function(err, data){ //suppose
if(err){
def.reject();
}
else {
def.resolve(data); //pass the data to the .then() handler.
}
});
return def.promise;
}
function someAsyncFuncName2() {
var def = q.defer();
//async function
setTimeout(function(){ //suppose
//do something
if(good) {
def.resolve();
} else {
def.reject();
}
}, 1000); //arbitrary timeout of 1 second
return def.promise;
}
USAGE:
q.all([someAsyncFuncName1('/api-1'), someAsyncFuncName2()])
.then(function() {
//final handler
});
On a similar line of thought one can use q.allSettled() if you want to wait for all promises to return.
Hope this helps.
---EOF OLD ANSWER---
First of all, if async functions used in PromiseA don't return promises, you need to promisify them. You can do that with Promise constructor, but it's much better to use libraries, such as bluebird with their promisify methods.
Let's imagine, that we have two functions getUserIdsAsync and getUserAsync. The first on returns a list of user ids, getUserAsync returns an user data by userId. And you need to get a list of users by their ids. The code of PromiseA could look so:
var promiseA = function() {
return getUserIdsAsync()
.then(userIds => {
let ops = users.map(uid => getUserAsync(uid));
return Promise.all(ops);
});
}
The following snippet shows a solution without using any external library like bluebird. It follows the code snippet in your question (which seems to be more complicate than needed).
You have to collect all api promisses in an array. Then you can call Promise.all() to get a Promise for the end of all api promisses. Then you can do some final stuff, like parsing the result of each promise and continue afterwards.
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
var apiEndpoint = function (name) {
return new Promise( (resolve, reject) => {
setTimeout(() => resolve('API ' + name + ' job done'), 1000);
});
}
var promiseA = function() {
return new Promise( (resolve, reject) => {
const promisses = [];
for (var i=1; i < getRandomInt(3,6); i++) {
// 1. Establish objects needed from one API endpoint
promisses.push(apiEndpoint('This is number ' + i));
}
Promise.all(promisses).then( results => {
// do final stuff
for (const s of results) {
// 2. Call API endpoint for each object and parse
console.log(s);
}
// continue ...
// 3. Only then continue to next promise
resolve('now it is finished');
}).catch( err => reject(err) );
});
}
var finalPromise = function() {
return new Promise( (resolve, reject) => {
console.log('finalPromise');
resolve();
});
}
promiseA()
.then( () => finalPromise())
.catch(err => console.log(err) );
Please hold in mind that this solution is not easy to read. Using external libraries or reducing promisses can improve readability. Maybe you should take a look to the async/await pattern to get a much more better (readable) solution.
Here is a solution with async/await:
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
const apiEndpoint = function (name) {
return new Promise( (resolve, reject) => {
setTimeout(() => resolve('API ' + name + ' job done'), 1000);
});
}
async function promiseParallel () {
const promisses = [];
for (let i = 1; i < getRandomInt(3,6); i++) {
promisses.push(apiEndpoint('This is number ' + i));
}
for (const p of promisses) {
const x = await p;
console.log(x);
}
return ('everything is done');
}
promiseParallel().then( result => {
console.log(result);
}).catch( err => console.log(err) );
If you want call the promisses sequentially you can replace with:
async function promiseSequ () {
for (let i = 1; i < getRandomInt(3,6); i++) {
const x = await apiEndpoint('This is number ' + i);
console.log(x);
}
return ('everything is done');
}
Please look at the code below. the request module is this one(https://www.npmjs.com/package/request)
var urlArray = []; // URL in this level
foo ("xyz.com", false);
function crawl (url, finished) {
request(url, function (error, response, body) {
if (finished == true) { return; }
// do some work on body (including getting getting n number of new URLS from
// the body) and set finished = true if we find what we are looking for.
// for each new url urlArray.push(newURL);
// for each new url call crawl(newurl, finished);
// Now How can I know when ALL these requests have finished?
// so that I can have a urlArray corresponding to this level of crawling tree and
// do some work before starting next level of crawl.
});
}
use Promises.
Check out the Q library (specifically I pointed to the methods you need):
Promise creation:
https://github.com/kriskowal/q/wiki/API-Reference#qdefer
var promise = Q.defer();
doAsyncStuff(callbackOfAsync);
return promise.promise;
functioncallbackOfAsync(isSuccess){
if(isSuccess){
promise.resolve();
}
else{
promise.reject();
}
}
Wait for multiple promises:
https://github.com/kriskowal/q/wiki/API-Reference#promise-for-array-methods
Q.all([getFromDisk(), getFromCloud()]).done(function (values) {
assert(values[0] === values[1]); // values[0] is fromDisk and values[1] is fromCloud
});
I don't really understand your question, but I guess you will need Promises. I assume you are using NodeJS.
function makeRequest (url) {
return new Promise(function (resolve, reject) {
request(url, function (err, response, body) {
if (err || response.statusCode !== 200)
reject(err || body);
else
resolve(body);
}
})
}
this function returns a promise. You can use it this way:
var request = makeRequest('url.com');
request.then(function (urls) {
// this is called if no error occured
urls.forEach(function (url) {
console.log (url);
});
}, function (error) {
// this is called on error
console.log (error);
});
If you want to wait for multiple requests to be answered to perform an action, use Promise.all:
var requests = [makeRequest(url1), makeRequest(url2), makeRequest(url3)];
Promise.all(requests).then(function (data) {
// everything is done
console.log(data);
});
I didn't test the code, but I hope you get the idea.
To answer your question specifically, the following flow of logic should work for you, I have added comments to help it make sense to you:
var urlArray = []; // URL in this level
var finished = false;
foo("xyz.com", false);
function start() {
while (urlArray.length) {
crawl(urlArray.pop());
}
}
function crawl(url) {
request(url, function (error, response, body) {
if (finished) {
return;
}
// 1. at this point, a given batch of crawls have all started
// AND urlArray is empty.
// 2. do some work on body (including getting getting n number of new URLS from
// the body) and set finished = true if we find what we are looking for.
// 3. for each new url urlArray.push(newURL);
// 4. start crawling on new batch of URL set
start();
});
}
All the request callbacks will be executed after start() completes, this guarantees that urlArray will be empty then.
If processing of one crawl response indicates (by setting finished = true;) that what you're looking for has been found all other processing of responses will terminate as soon as they begin.
Otherwise, reponse is processed and a new batch of urls are set for crawling. You call start() to begin crawling each.
It would help you also (as suggested in the other answer) if you acquainted yourself with the concept of Promises.
I think this is a really stupid question but I'm having a hard time wrapping my head around promises.
I'm using Q (for nodejs) to sync up a couple of async functions.
This works like a charm.
var first = function () {
var d = Q.defer();
fs.readdir(path,function(err,files){
if(err) console.log(err);
d.resolve(files);
});
return d.promise;
};
var second = function (files) {
var list = new Array;
files.forEach(function(value, index){
var d = Q.defer();
console.log('looking for item in db', value);
db.query(
'SELECT * FROM test WHERE local_name =? ', [value],{
local_name : String,
},
function(rows) {
if (typeof rows !== 'undefined' && rows.length > 0){
console.log('found item!', rows[0].local_name);
d.resolve(rows[0]);
} else {
var itemRequest = value;
getItemData(itemRequest);
}
}
);
list.push(d.promise);
});
return Q.all(list);
};
first()
.then(second)
.done(function(list){
res.send(list);
});
The problem I have is with this little function:
getItemData(itemRequest)
This function is filled with several of callbacks. The promise chain runs through the function just fine but ignores all the callbacks I use ( eg several XHR calls I make in the function).
A simplified version of the function looks like this (just to give you an idea):
function getItemData(itemRequest){
helper.xhrCall("call", function(response) {
var requestResponse = JSON.parse(response)
, requestInitialDetails = requestResponse.results[0];
downloadCache(requestInitialDetails,function(image) {
image = localImageDir+requestInitialDetails.image;
helper.xhrCall("call2", function(response) {
writeData(item,image,type, function(){
loadData(item);
});
});
} else {
writeData(item,image,type, function(){
loadData(item);
});
}
});
});
The xhr function I use looks like this:
xhrCall: function (url,callback) {
var request = require("request")
, colors = require('colors');
request({
url: url,
headers: {"Accept": "application/json"},
method: "GET"
}, function (error, response, body) {
if(!error){
callback(body);
}else{
console.log('Helper: XHR Error',error .red);
}
});
}
So my questions:
Can I leave the function unaltered and use the callbacks that are in place ánd the promise chain?
Or do I have to rewrite the function to use promises for the XHR?
And if so, How can I best write my promise chain? Should I reject the initial promise in the forEach?
Again, sorry if this is a really stupid question but I don't know what the right course of action is here.
Thanks!
[EDIT] Q.nfcall, I don't get it
So I've been looking into Q.nfcall which allows me to use node callbacks. Bu I just don't understand exacly how this works.
Could someone give a simple example how I would go about using it for a function with several async xhr calls?
I tried this but as you can see I don't really understand what I'm doing:
var second = Q.nfcall(second);
function second (files) {
[EDIT 2]
This is the final funcction in my getitemdata function callback chain. This function basically does the same as the function 'second' but I push the result directly and then return the promise. This works as stated, but without all the additional callback data, because it does not wait for the callbacks to return with any data.
function loadData(item) {
var d = Q.defer();
db.query(
'SELECT * FROM test WHERE local_name =? ', [item],{
local_name : String,
},
function(rows) {
if (typeof rows !== 'undefined' && rows.length > 0){
list.push(d.promise);
}
}
);
});
return Q.all(list);
};
Your answer is not really clear after your second edit.
First, on your orignal question, your getItemData has no influence on the promise chain.
You could change you the function's call signature and pass your deferred promise like so.
getItemData(itemRequest, d)
and pass this deferred promises all the way to your xhrCall and resolve there.
I would re-write your whole implementation and make sure all your functions return promises instead.
Many consider deferred promises as an anti-pattern. So I use use the Promise API defined in harmony (the next javascript)
After said that, I would re-implement your original code like so (I've not tested)
var Promise = Promise || require('es6-promise').Promise // a polyfill
;
function errHandler (err){
throw err
}
function makeQuery () {
var queryStr = 'SELECT * FROM test WHERE local_name =? '
, queryOpt = {local_name: String}
;
console.log('looking for item in db', value)
return new Promise(function(resolve, reject){
db.query(queryStr, [value], queryOpt, function(rows) {
if (typeof rows !== 'undefined' && rows.length > 0){
console.log('found item!', rows[0].local_name);
resolve(rows[0]);
} else {
// note that it returns a promise now.
getItemData(value).then(resolve).catch(errHandler)
}
})
})
}
function first () {
return new Promise(function(resolve, reject){
fs.readdir(path, function(err, files){
if (err) return reject(err)
resolve(files)
})
})
}
function second (files) {
return Promise.all(files.map(function(value){
return makeQuery(value)
});
}
first()
.then(second)
.then(res.send)
.catch(errHandler)
Note that there is no done method on the Promise API.
One down side of the new Promise API is error handling. Take a look at bluebird.
It is a robust promise library which is compatible with the new promise API and has many of the Q helper functions.
As far as I can tell, you need to return a promise from getItemData. Use Q.defer() as you do in second(), and resolve it when the callbacks complete with the data. You can then push that into list.
To save code, you can use Q.nfcall to immediately call a node-style-callback function, and return a promise instead. See the example in the API docs: https://github.com/kriskowal/q/wiki/API-Reference#qnfcallfunc-args