var img1 = document.querySelector('.img-1'); img1.addEventListener('load', function() { // , }); img1.addEventListener('error', function() { // , });
complete
images property: var img1 = document.querySelector('.img-1'); function loaded() { // , } if (img1.complete) { loaded(); } else { img1.addEventListener('load', loaded); } img1.addEventListener('error', function() { // , });
img1.callThisIfLoadedOrWhenLoaded(function() { // }).orIfFailedCallThis(function() { // }); // … whenAllTheseHaveLoaded([img1, img2]).callThis(function() { // }).orIfSomeFailedCallThis(function() { // });
ready
method that returns a promise, we could do the following: img1.ready().then(function() { // }, function() { // }); // … Promise.all([img1.ready(), img2.ready()]).then(function() { // }, function() { // });
thenable
to describe a promise of a similar object that has a then
method. But this term reminds me of former English football manager Teri Venables , so I will use it as seldom as possible. var promise = new Promise(function(resolve, reject) { // , , … if (/* .. */) { resolve("!"); } else { reject(Error("")); } });
resolve
and reject
. Everything is simple, inside the callback you perform any asynchronous operations, then you call resolve
in case of success, or reject
in case of failure.throw
in good old JavaScript, rejects do not have to pass an error object. The advantage of creating an Error object is that debugging code, having a call stack trace in the console, is much more pleasant. promise.then(function(result) { console.log(result); // " !" }, function(err) { console.log(err); // : "" });
then
method, as it should be for a Promise-like object (in Promise terminology it is also called thenable
). There is also a Promise.cast
method that erases the boundaries between embedded and custom Promise-like objects. So, if you use a library that returns promises of type Q, that's fine, they will work fine with native JavaScrip promises. var jsPromise = Promise.cast($.ajax('/whatever.json'));
$.ajax
returns Deferred. But as long as he has a then
method, Promist.cast
can turn it into a real promise. Be that as it may, at times Deffered passes too many arguments to its callback: var jqDeferred = $.ajax('/whatever.json'); jqDeferred.then(function(response, statusText, xhrObj) { // ... }, function(xhrObj, textStatus, err) { // ... }); JS : jsPromise.then(function(response) { // ... }, function(xhrObj) { // ... });
reject
.XMLHttpRequest
first candidate, but for now let's write a simple function for making a GET request: function get(url) { // . return new Promise(function(resolve, reject) { // XHR var req = new XMLHttpRequest(); req.open('GET', url); req.onload = function() { // 404' // if (req.status == 200) { // resolve(req.response); } else { // , // reject(Error(req.statusText)); } }; // req.onerror = function() { reject(Error("Network Error")); }; // req.send(); }); }
get('story.json').then(function(response) { console.log("!", response); }, function(error) { console.error("!", error); });
XMLHttpRequest
, which makes me very happy, because The nauseous form of XMLHttpRequest
'camel's notation poisons my life.then
this is not the end of the story, you can link calls then
for end-to-end transformation of return values ​​or perform additional asynchronous actions one by one. var promise = new Promise(function(resolve, reject) { resolve(1); }); promise.then(function(val) { console.log(val); // 1 return val + 2; }).then(function(val) { console.log(val); // 3 });
get('story.json').then(function(response) { console.log("!", response); });
responseType
our answer, but we can also send a stroll through the wonderful world of promises: get('story.json').then(function(response) { return JSON.parse(response); }).then(function(response) { console.log(" JSON!", response); });
JSON.parse
takes one argument and returns the processed value, and we can simply pass a reference to it: get('story.json').then(JSON.parse).then(function(response) { console.log(" JSON!", response); });
getJSON
sugar function: function getJSON(url) { return get(url).then(JSON.parse); }
getJSON
still returns the promise after it pulls out the data and parses the JSON response.then
calls to perform asynchronous actions sequentially.then
callback, a little bit of magic happens. If you return any value, this value will be passed to the callback function next then
. And if you return something like a promise, the next will then
wait for it and call the callback only when it is fulfilled. For example: getJSON('story.json').then(function(story) { return getJSON(story.chapterUrls[0]); }).then(function(chapter1) { console.log(" !", chapter1); });
story.json
, and when we receive a set of URLs in the response, we request for the first one. Here we clearly see how far an apple can roll away from the apple tree, the advantage of promises over the usual pattern of callbacks hurts the eyes. You can take out the logic of requesting an article to a separate method: var storyPromise; function getChapter(i) { storyPromise = storyPromise || getJSON('story.json'); return storyPromise.then(function(story) { return getJSON(story.chapterUrls[i]); }) } // : getChapter(0).then(function(chapter) { console.log(chapter); return getChapter(1); }).then(function(chapter) { console.log(chapter); });
story.json
until the first call to getChapter
, and the following calls to getChapter
re-use the promise of loading the story that has already been fulfilled and do not make additional requests. Oh, those Promises!then
takes two arguments, one for successful completion, the other is called in case of an error (fulfill and reject in the terminology of promises): get('story.json').then(function(response) { console.log("!", response); }, function(error) { console.log("!", error); });
catch
: get('story.json').then(function(response) { console.log("!", response); }).catch(function(error) { console.log("!", error); });
then(undefined, func)
. Note that the two pieces of code above are not the same, the latter is equivalent to the following: get('story.json').then(function(response) { console.log("!", response); }).then(undefined, function(error) { console.log("!", error); });
then
call chain (or catch
, which is almost the same) until the first error handler is encountered. In the case of then(func1, func2)
, func1
and func2
will never be called both. But in the chain of then(func1).catch(func2)
, both functions can be called if the promise returned from func1
fails (reject). Joke with the following piece of code: asyncThing1().then(function() { return asyncThing2(); }).then(function() { return asyncThing3(); }).catch(function(err) { return asyncRecovery1(); }).then(function() { return asyncThing4(); }, function(err) { return asyncRecovery2(); }).catch(function(err) { console.log("Don't worry about it"); }).then(function() { console.log("All done!"); });
try/catch
, the error that occurred in the try
block is immediately passed to the catch
. Here is a block diagram of what is happening in the code above (I love block diagrams): var jsonPromise = new Promise(function(resolve, reject) { // JSON.parse // JSON, reject': resolve(JSON.parse("This ain't JSON")); }); jsonPromise.then(function(data) { // : console.log("!", data); }).catch(function(err) { // : console.log("!", err); });
then
callback: get('/').then(JSON.parse).then(function() { // , '/' JSON // JSON.parse console.log("!", data); }).catch(function(err) { // : console.log("!", err); });
catch
to alert the user about the error: getJSON('story.json').then(function(story) { return getJSON(story.chapterUrls[0]); }).then(function(chapter1) { addHtmlToPage(chapter1.html); }).catch(function() { addTextToPage(" "); }).then(function() { document.querySelector('.spinner').style.display = 'none'; });
story.chapterUrls[0]
fails (http 500 or the user has gone offline), all subsequent callbacks called upon success will not be executed, such as the JSON parser included in getJSON
, the callback adding the first chapter to the page , too, will be ignored. Execution will immediately go to the first error handling callback. As a result, the user will see the message “It is impossible to display the chapter” if in any of the previous callbacks something goes wrong.try/catch
error will be intercepted, and the program will continue its execution, so we will successfully hide the loading indicator, which is what we need. Here’s how it would look in a synchronous blocking version: try { var story = getJSONSync('story.json'); var chapter1 = getJSONSync(story.chapterUrls[0]); addHtmlToPage(chapter1.html); } catch (e) { addTextToPage(" "); } document.querySelector('.spinner').style.display = 'none';
getJSON
method: function getJSON(url) { return get(url).then(JSON.parse).catch(function(err) { console.log("getJSON ", url, err); throw err; }); }
try { var story = getJSONSync('story.json'); addHtmlToPage(story.heading); story.chapterUrls.forEach(function(chapterUrl) { var chapter = getJSONSync(chapterUrl); addHtmlToPage(chapter.html); }); addTextToPage("All done"); } catch (err) { addTextToPage("- : " + err.message); } document.querySelector('.spinner').style.display = 'none';
then
to perform tasks one after another. getJSON('story.json').then(function(story) { addHtmlToPage(story.heading); // TODO: story.chapterUrls }).then(function() { // ! addTextToPage(" "); }).catch(function(err) { // , addTextToPage("- : " + err.message); }).then(function() { // document.querySelector('.spinner').style.display = 'none'; });
story.chapterUrls.forEach(function(chapterUrl) { // getJSON(chapterUrl).then(function(chapter) { // addHtmlToPage(chapter.html); }); });
forEach
has nothing to do with asynchrony, and our chapters will be added to the page in random order as it loads, approximately, as it was written “Pulp Fiction”. We do not have “Pulp Fiction”, so let's fix it ...chaptersUrls
array into a line of promises. We can do this as follows: // , var sequence = Promise.resolve(); // story.chapterUrls.forEach(function(chapterUrl) { // sequence = sequence.then(function() { return getJSON(chapterUrl); }).then(function(chapter) { addHtmlToPage(chapter.html); }); });
Promise.resolve
factory method, which creates an immediate fulfilled promise with the value that you give to it. If you give him something like a promise (something that has a then
method), he will return a copy of it. If you call Promise.resolve
without an argument, as in our example, it returns a successfully fulfilled promise with the value undefined.Promise.reject(val)
, which returns a promise, completed with an error, with the value you give it (or undefined
).array.reduce
: // story.chapterUrls.reduce(function(sequence, chapterUrl) { // return sequence.then(function() { return getJSON(chapterUrl); }).then(function(chapter) { addHtmlToPage(chapter.html); }); }, Promise.resolve());
Promise.resolve()
, but with each subsequent call of the callback, the result of the previous call is passed to it. array.reduce
it is very convenient to use when you need to cast an array to a single value, for example, a promise, as in our case. getJSON('story.json').then(function(story) { addHtmlToPage(story.heading); return story.chapterUrls.reduce(function(sequence, chapterUrl) { return sequence.then(function() { // … return getJSON(chapterUrl); }).then(function(chapter) { // addHtmlToPage(chapter.html); }); }, Promise.resolve()); }).then(function() { // ! addTextToPage(" "); }).catch(function(err) { // , addTextToPage("- : " + err.message); }).then(function() { // document.querySelector('.spinner').style.display = 'none'; });
Promise.all(arrayOfPromises).then(function(arrayOfResults) { //... });
Promise.all
accepts an array of promises and returns one promise that will be fulfilled only when all promises are completed successfully. This general promise will return to the callback an then
array of each results in the order in which you gave them. getJSON('story.json').then(function(story) { addHtmlToPage(story.heading); // return Promise.all( // // getJSON story.chapterUrls.map(getJSON) ); }).then(function(chapters) { // … chapters.forEach(function(chapter) { // … addHtmlToPage(chapter.html); }); addTextToPage(" "); }).catch(function(err) { addTextToPage("- : " + err.message); }).then(function() { document.querySelector('.spinner').style.display = 'none'; });
getJSON('story.json').then(function(story) { addHtmlToPage(story.heading); // // getJSON // , . return story.chapterUrls.map(getJSON) .reduce(function(sequence, chapterPromise) { // , // return sequence.then(function() { return chapterPromise; }).then(function(chapter) { addHtmlToPage(chapter.html); }); }, Promise.resolve()); }).then(function() { addTextToPage("All done"); }).catch(function(err) { addTextToPage("Argh, broken: " + err.message); }).then(function() { document.querySelector('.spinner').style.display = 'none'; });
function *addGenerator() { var i = 0; while (true) { i += yield i; } }
yield
is our return / restore point. We can use the above function, for example, like this: var adder = addGenerator(); adder.next().value; // 0 adder.next(5).value; // 5 adder.next(5).value; // 10 adder.next(5).value; // 15 adder.next(50).value; // 65
yield
to wait for the fulfillment of the promise. function spawn(generatorFunc) { function continuer(verb, arg) { var result; try { result = generator[verb](arg); } catch (err) { return Promise.reject(err); } if (result.done) { return result.value; } else { return Promise.cast(result.value).then(onFulfilled, onRejected); } } var generator = generatorFunc(); var onFulfilled = continuer.bind(continuer, "next"); var onRejected = continuer.bind(continuer, "throw"); return onFulfilled(); }
spawn(function *() { try { // 'yield' effectively does an async wait, // returning the result of the promise let story = yield getJSON('story.json'); addHtmlToPage(story.heading); // Map our array of chapter urls to // an array of chapter json promises. // This makes sure they all download parallel. let chapterPromises = story.chapterUrls.map(getJSON); for (let chapterPromise of chapterPromises) { // Wait for each chapter to be ready, then add it to the page let chapter = yield chapterPromise; addHtmlToPage(chapter.html); } addTextToPage("All done"); } catch (err) { // try/catch just works, rejected promises are thrown here addTextToPage("Argh, broken: " + err.message); } document.querySelector('.spinner').style.display = 'none'; });
about:flags
.let
, for-of
. And it shows how we can write simple asynchronous code with normal try/catch
.Source: https://habr.com/ru/post/209662/
All Articles