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.reduceit 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.allaccepts 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 thenarray 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; } } yieldis 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 yieldto 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