Asynchronous JavaScript programming
Article content output source: pull hook education front-end high salary training camp
It is well known that the current mainstream JavaScript environment is JavaScript code executed in single-threaded mode. The reason why JavaScript works in single-threaded mode is related to its original intention. JavaScript was originally a scripting language running on the browser to realize dynamic interaction on the page. Dom operation is the core of page interaction, which determines that it must use single thread. Otherwise complex thread synchronization problems can occur.
Imagine if we had multiple threads working in JavaScript at the same time, and one thread modified a DOM element while the other thread deleted it at the same time. The browser wouldn’t know which thread was working on it, so to avoid thread synchronization, JavaScript was designed to work in single-threaded mode from the beginning, and this has become one of the most central features of the language.
By single thread, WE mean that there is only one thread responsible for executing code in the JS execution environment. Only one task can be executed at a time. Multiple tasks need to be queued and completed one by one. The biggest advantage of this mode is that it is safer and simpler, while the disadvantage is also obvious. If a particular time-consuming task is encountered, subsequent tasks must queue up for the end of the task, which will lead to the execution of the whole program will be delayed and the situation of suspended animation will occur.
To solve the problem of time-consuming task blocking, JavaScript divides the execution mode of the task into two types, synchronous mode and asynchronous mode.
Synchronous and asynchronous modes
Synchronous mode
Synchronous mode means that the tasks in the code are executed in sequence. The latter task must wait for the previous task to finish and then execute the code in the order written.
Asynchronous mode
Don’t wait for the end of one task before moving on to the next. For a time-consuming task, the next task is immediately executed after it is enabled. The subsequent logic of a time-consuming task is defined in the form of a callback function. The callback function here is automatically executed when the time-consuming task is completed.
Callback function: a function defined by the caller and given to the executor to execute.
Without asynchronous mode, a single-threaded JavaScript language cannot handle a large number of time-consuming tasks simultaneously. But for developers, the biggest problem with asynchronous mode is that the code is executed out of order.
Event loops and message queues
The JavaScript thread performs the synchronization task first, makes an asynchronous call when it encounters an asynchronous task (eg: setTimeout), and then continues the synchronization task. At the same time, after the asynchronous calling thread executes the asynchronous task, it puts the callback of the asynchronous task into the message queue. After the synchronization task is completed, the Event Loop will search for tasks in the message queue and execute the tasks in the message queue one by one.
Several ways of asynchronous programming
Promise asynchronous schemes, macro/micro task queues
A Promise is an object that indicates whether an asynchronous task succeeds or fails when it finally ends. This is like a promise. At the beginning, it is a state — Pending. This is a pity after success and Rejected after failure. After the commitment is clear, the corresponding task will be executed, such as onFilfilled and onRejected.
Basic usage
const promise = new Promise(function (resolve, Reject) {// reject(new Error(' reject '))}) promise. Then (function (value) {// reject(new Error(' reject '))}) promise. { console.log('resolved', value) return 1 }, function (error) { console.log('rejected', error) }).then(function(value){ console.log(value) // 1 })Copy the code
- The then method of the Promise object returns a brand new Promise object, so you can use chain calls
- The subsequent THEN method registers the callback for the Promise returned by the previous THEN
- The return value of the callback function from the previous THEN method is used as an argument to the callback from the later THEN method
- If a Promise is returned in the callback, then callbacks to subsequent then methods wait for the Promise to end
Exception handling
function ajax(url) { return new Promise(function (resolve, reject) { var xhr = new XMLHttpRequest() xhr.open('GET', url) xhr.responseType = 'json' xhr.onload = function () { if (this.status === 200) { resolve(this.response) } else { Reject (new Error(this.statustext))}} xhr.send()})} ajax('/ API /users.json').then(function) onFulfilled(value) { console.log('onFulfilled', value) }, function onRejected(error) { console.log('onRejected', Json '). Then (function ondepressing (value) {console.log(' ondepressing ', value) }) .catch(function onRejected(error) { console.log('onRejected', error) })Copy the code
The second callback to then catches only the exception thrown by the previous one. A catch catches only the exception thrown by the previous one because each THEN returns a promise object. That is, a catch catches exceptions before a catch on the chain.
Promise static method
- Promise.resolve()
- Promise.reject()
Promise parallel execution
- Promise.all()
Var Promise = promise. all([ajax('/ API /user.json')), Ajax (' API /posts.json')]) // If all the asynchronous tasks succeed, the Promise will succeed. Then (function (values) {function (values) { Console.log (values)}). Catch (function (error) {console.log(error)})Copy the code
- Promise.race()
Promise.race() also combines multiple Promise objects to return a new Promise object, but unlike all:
All does not end until all tasks end
Race only waits for the first completed task, meaning that as soon as one task completes, the new promise object completes.
const request = ajax('/api/posts.json') const timeout = new Promise((resolve, reject => { setTimeout(() => reject(new Error('timeout')), 500)})) // promise.race () combines asynchronous tasks and returns a new Promise object // If one of the asynchronous tasks completes (successfully or failed), Then if the request is successful within 500 milliseconds, it returns success, using the. Then method. Race ([require, timeout]).then(value => {console.log(value)}).catch(error => {console.log(error)})Copy the code
const p = Promise.all([p1,p2,p3])
p.then(() => {})
.catch(err => {})
Copy the code
- Promise.all(): p1, P2, p3 all return success, p will return success, p1, P2, p3 any return failure, p will return failure. After a failure, other asynchronous tasks continue to execute.
- Promise.race(): p1, P2, p3 returns success, p returns success, p1, P2, p3 returns failure, p returns failure After a failure, other asynchronous tasks continue to execute.
- Promise. AllSettled () : This will be a big pity when P1, P2 and P3 are all executed, no matter whether they succeed or fail. Listen for an array of arguments received by the function
[{status:'fulfilled', value: 42}, {status:'rejeceted}, reason:-1]
- Promise.any(): p1, p2, p3 returns success, and p returns failure if p1, P2, p3 all fail
Promise execution timing
console.log('global start') setTimeout(() => { console.log('setTimeout') }, 0) Promise.resolve() .then(() => { console.log('promise') }) .then(() => { console.log('promise 2') }) .then(() => { console.log('promise 3') }) console.log('global end') // global start // global end // promise // promise 2 // promise 3 // setTimeoutCopy the code
As mentioned earlier, the callbacks go into the callback queue and are executed sequentially. We might expect to print setTimeout first and then promise, but this is not the case. This is because JS divides tasks into macro tasks and micro tasks. Microtasks jump the queue and are executed at the end of the round.
Most asynchronous tasks are macro tasks.
Microtasks include Promise, MutationObserver, and Process.nexttick
Generator asynchronous scheme, Async/Await syntax sugar
The basic use
* function * foo() {console.log('start') // Yield returns a value, which is what the next method returns // yield does not end the execution of the generator, Just pause // if the next method passes an argument, // yield 'foo' // const res = yield 'foo' // console.log(res) // bar try {const res = yield 'foo' Console. log(res) // bar} catch (e) {console.log(e)}} // The call generator is not executed immediately, Instead, get a generator object const generator = foo() // Call next and the function body will execute const result = generator.next() // Return a done attribute in the result, Log (result) //{value: "foo", done: Next ('bar') // If the generator's throw method is called, execution will continue. But it will throw an exception // Use the try{}catch(){} statement inside the generator to receive the exception generator.throw(new Error(' generator Error '))Copy the code
function* main() {
try {
const users = yield ajax(url1)
console.log(users)
const posts = yield ajax(url2)
console.log(posts)
} catch (e) {
console.log(e)
}
}
function co(generator) {
const g = generator()
function handleResult(result) {
if (result.done) return
result.value.then(data => {
handleResult(g.next(data))
}, error => {
g.throw(error)
})
}
handleResult(g.next())
}
co(main)
Copy the code
Async/Await syntax sugar
// Change generator * to async, Async function main() {try {const users = await Ajax (URL1) console.log(users) const posts = await Ajax (url2) console.log(posts)} catch (e) {console.log(e)}} Const Promise = main() promise.then(() => {console.log('all completed')})Copy the code
Hand Promise
/** * A Promise is a class that passes in a function as an argument and calls it directly. Rejected * The state is modified after the resolve and Reject calls and cannot be modified after the state is modified * The parameters in resolve and Reject are recorded as parameters in the success and failure callbacks to the THEN methods * If the promise execution fails, To catch errors, you can use a try catch to catch * where you need to catch errors include the function executor passed in by promise, */ const PENDING = 'PENDING' const REJECRED = 'REJECRED' class MyPromise { Constructor (fn) {// Promise (); Resolve and reject fn(this.resolve, reject) Reject)} Catch (err) {this.reject(err)}} // Define the initial status status = PENDING // then method successful callback parameter value = undefined // then SCallback = [] fCallback = [] resolve = (value) => {// If state is not pending, If (this.status! This. Status = this. Value = value // If there is a stored successful callback, then call, // this.scallBack && this.scallBack (value) while (this.scallback.length) this.scallback.shift ()()} reject = (error) => {// If the status is not pending, do not modify if (this.status! This. status = REJECRED this.error = error // If there is a stored failure callback, call, // this.fcallback && this.fcallback (error) while (this.fcallback.length) this.fcallback.shift ()()} /** * then Method parameters are success callback and failure callback * Determine which callback to execute based on the state * If the call is asynchronous, then method is executed in the state or pending, The same promise can be called by multiple then calls, so there can be multiple sets of successful and failed callbacks. Asynchronous callbacks can be stored as arrays. So it returns a promise object, The value returned by the callback is used as an argument to the next THEN method. * The promise returned by the then method cannot be itself. * The promise returned by the then method cannot return itself. The next THEN gets the result that this then should get * so when the then doesn't pass arguments, SCallback = value => value, FCallback = error => {throw error}) {let newPromise = new MyPromise(resolve, reject) => { If (this.status === depressing) {setTimeout(() => {try {// Const x = sCallback(this.value)) // If the return value is a PROMISE object, do resolve and reject according to the result of the promise. // If the return value is a promise object, do resolve and reject according to the result of the promise. So I'm going to pass in newPromise and say, "// But you can't actually get newPromise here, you can put this code in setTimeout." // I'm going to put this code in setTimeout not to delay, but just to wait for newPromise to be created and referenced, ThenValue (newPromise, x, resolve, reject)} catch (err) {reject(err)}}, 0) } else if (this.status === REJECRED) { setTimeout(() => { try { const x = fCallback(this.error) thenValue(newPromise, X, resolve, reject)} catch (err) {reject(err)}}, 0)} else { This.scallback.push (() => {setTimeout(() => {try {const x = sCallback(this.value)) thenValue(newPromise, x, resolve, reject) } catch (err) { reject(err) } }, 0) }) this.fCallback.push(() => { setTimeout(() => { try { const x = fCallback(this.error) thenValue(newPromise, x, Resolve, reject)} catch (err) {reject(err)}}, 0)})} return newPromise} /** * The finally method executes a callback regardless of whether a promise succeeds or fails. * Finally passes down the result of a promise. * Finally can be implemented using the then method The then method returns a new Promise. * If finally returns a Promise, wait until the promise has a result. Then */ finally(callback) {return this.then(value => {return myPromise.resolve (callback()). Then (() => value) }, err => {return mypromise.resolve (callback()). Then (() => {throw err})})} Return this. Then (undefined, callback)} /** * Implement an all method that passes an array of asynchronous calls. Return a new Promise object * all asynchrony successes in the array, return the result as an array, */ static all(args) {let results = [] let index = 0 return MyPromise((resolve, resolve) Reject) => {function addData(key, value) {results[key] = value // Can not be judged by the length of results, because results is not assigned by push method, Index ++ if (index == args.length) {resolve(results)}} for (let I = 0; i < args.length; I++) {// check whether it is a promise object or a normal value, If (args[I] instanceof MyPromise) {// Promise args[I]. Then (value => {addData(I, value)}, Reject)} else {addData(I, reject) Args [I])}}})} /** * implement a promise. resolve method * promise. resolve method * Return it * if it is a normal value, Static resolve(value) {if (value instanceof MyPromise) return value return new MyPromise(resolve => {resolve(value)})} /** * promise. reject Reject */ reject(reason) {return new Promise((resolve, reject) => { reject(reason) }) } } function thenValue(newPromise, x, resolve, Reject) {if (newPromise === x) return reject(new TypeError('then method cannot return itself ')) if (x instanceof MyPromise) {// Resolve (x)} else {// resolve(x)}Copy the code