Reputation: 1
I am trying to understand Javascript promises and so far this is what I got. Basically I am trying to simulate ajax requests with setInterval and setTimeout to understand how I can use:
promise.progress
promise.done
promise.fail
promise.always
promise.catch
The console.log() example below shows everything except fail and catch. I have no idea how to break the code to trigger catch on a fail? Or fail IS catch? Or catch comes after fail??!
Can you help me break this code in all of the ways required to catch and handle a fail?
$(document).ready(function() {
console.log('1. Document is ready.');
var timer;
var i = 1;
// Declare and Call the promise.
var promise = myProcess();
promise.progress(function() {
// Receives updates from deferred.notify();
// deferred.notify updates promise.progress on every Tick of timer = setInterval
console.clear();
console.log('promise.progress.. ' + i++);
});
promise.done(function() {
// deferred.resolve triggers at the end of setTimeout
console.log('promise.done');
});
promise.fail(function(error) {
// Executes on fail...
console.log('promise.fail');
});
promise.always(function() {
// Executes always...
console.log('promise.always');
//console.log(myProcess());
});
function myProcess() {
var deferred = $.Deferred();
timer = setInterval(function() {
// Tick every 1 second...
deferred.notify();
}, 1000);
// until the 3rd second...
setTimeout(function() {
clearInterval(timer);
deferred.resolve();
}, 3000);
return deferred.promise();
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
Upvotes: 0
Views: 1423
Reputation: 8589
I'd suggest reading some articles about promises. Those will be able to explain way better than I can. Also try working with the 'real' promises on a modern browser. Else you'll have to relearn some things once you switch away from JQuerys deferred(), since it's a non-standard implementation of promises, so the API will differ. But let's see what we can do with your code.
1) If we want to test all the possible outcomes of a promise, we need to actually do something inside a promise that can either succeed or fail. An ajax call is a good example for it. So let's create a function that does work and change the myProcess function a bit so it makes an ajax call to an uri we provide.
var myProcess = function myProcess( uri ) {
var deferred = $.Deferred();
// Keep the timer so the .progress() method will get triggered.
// In the example of an ajax call, you'd probably want to bind this to either the readystatechange event or to some work the callback of $.get() executes.
timer = setInterval(function() {
// Tick every 1 second...
deferred.notify();
}, 1000);
// Make an ajax call. If it suceeeds, resolve, else reject
// Since an ajax call is already async, we don't need to simulate the waiting with the timeout.
$.get( uri, function( data ) {
// remove the timer again once the ajax result returns.
clearInterval( timer );
if ( data ) deferred.resolve( data );
else deferred.reject( data );
} );
return deferred.promise();
};
2) Use the myProcess function and link some handlers to it. The .done() method is equivalent to the .then() method of standard Promises.
myProcess( 'http://some.api.uri/' )
// This will get triggered every second as long as the timer is active.
// If you want this to take longer for testing purposes, just make the ajax call take longer by adding a setTimeout again.
.progress( function onProgress() {
// The promise is still being resolved. Every second this gets triggered because we have deferred.notify() inside myProcess().
} )
// This will trigger once the ajax call succeeds succesfully and hence, .resolve() is triggered.
.done( function onResolved_1( data ) {
// Do something with the data you received from the ,get() call inside myProcess.
return JSON.parse( data );
} )
// This will trigger after the previous .done() function ( onResolved_1 ) has returned the data.
.done( function onResolved_2( dataReturnedFromPreviousDone ) {
// Do something with the data we parsed.
} )
// This will only trigger if the ajax call fails and hence, .reject() gets triggered.
.fail( function onFail( error ) {
// Do something with the error.
} )
// This will happen no matter if the promise gets resolved or rejected.
.always( function always() {
// stuff that always needs to happen.
} );
UPDATE: Native promises wrapped around JQuery.get()
In the end it's pretty simple. Any time you have something that happens async, just wrap it in a promise and continue from there with .then()
chaining.
So if we want to promisify JQuery .get(), we can use the following basic wrapper:
var getURI = function getURI( uri ) {
// Wrap the basic $.get() function into a native promsie object.
// The promise constructor expects a function that will take in the resolve and reject methods as parameters
return new Promise( function( resolve, reject ) {
$.get( uri, function( data ) {
if ( data ) resolve( data );
else reject( new Error( 'could not load uri: ' + uri ) );
} );
} );
};
Then again, we can use that function to grab us something. A page, data, anything that we can GET.
var getData = function getData( callback ) {
// Do any work that you want to happen before the GET call
console.log( 'this always happens before we fetch the data' );
// Make the request for the data. Let's assume it returns us a text structured as JSON.
getURI( 'https://postman-echo.com/get?foo1=bar1&foo2=bar2' )
// .then() accepts two functions. One to trigger after resolve() and one to trigger after reject()
.then(
function onResolve( data ) {
// do something with the data
// If the only thing you do with the data is returning the result of a function, you don't have to wrap that function.
// So if we only want to parse, we can just use .then( JSON.parse )
return data;
},
function onReject( error ) {
// do something with the error.
console.log( 'error thrown inside onReject() after getURI failed to get data.' );
console.error( error );
}
} )
// Anything you return from inside the onResolve() function will get used as the parameter for the next onResolve() function in the chain.
.then( function( data ) {
// do something else.
// instead of using a callback, you could also return the promise and keep chaining from there.
callback( data );
} );
};
.then()
functions do not require you to always provide a reject handler. If you only want to provide one error handler, you can use .catch()
at the end.
The only thing really missing is the .progress()
method. .first()
is any code before triggering a promise.
A full example of promises in action would be this:
var getURI = function getURI( uri ) {
return new Promise( function( resolve, reject ) {
$.get( uri, function( data ) {
if ( data ) resolve( data );
else reject( new Error( 'could not load uri: ' + uri ) );
} );
} );
};
var doThisFirst = function doThisFirst() {
console.log( 'this should be the first log to appear' );
};
var doThisLast = function doThisLast() {
console.log( 'this.should always be the last log' );
};
var addMeta = function addMeta( obj ) {
obj.meta = 'extra meta data';
return obj;
};
var renderData = function renderData( obj ) {
console.log( 'we received the following object and added a meta property to it: ' );
console.dir( obj );
};
var handleError = function handleError( error ) {
console.log( 'we found an error while fetching and augmenting some data' );
console.error( error );
};
var runApplication = function runApplication() {
doThisFirst();
getURI( 'https://postman-echo.com/get?foo1=bar1&foo2=bar2' )
.then( JSON.parse )
.then( addMeta )
.then( renderData )
.then( doThisLast )
.catch( handleError );
};
runApplication();
Upvotes: 3