This is the 9th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021

Callback function: a function defined by the caller and given to the executor to execute.

Disadvantages of callback functions: unreadable and out of order.

Asynchronous patterns are important for single-threaded JavaScript and are a core feature of JavaScript.

Callbacks are the foundation of all asynchronous programming in JavaScript.

If we had handled complex asynchronous logic directly with traditional callbacks, we would have had a lot of callback nested problems (callback hell).

At the same time, the code we write becomes very unreadable, and we often have a headache looking at it after a couple of days.

So, in order to solve this problem and improve the readability and maintainability of our code in asynchronous programming mode, this article focuses on the syntax and features of Promise to compensate for the inconvenience of using JavaScript callback functions in asynchronous programming mode.

  1. PromiseWhat is? What are the states?
  2. PromiseCase – Simulationajax
  3. PromiseThe chain call to
  4. Promise onRejectedPromise catch
  5. unhandledrejectionGlobal capturePromiseabnormal
  6. PromiseStatic method:resolve,reject
  7. PromiseParallel execution mode:Promise.all,Promise.race
  8. PromiseExecution timing issues, macro tasks vs. micro tasks

What is Promise? What are the states?

A Promise is essentially an object that indicates whether an asynchronous task succeeds or fails when it finishes.

There are three states of Promise: Pending, depressing and Rejected.

When the Promise status changes to “depressing”, the ondepressing callback function will be automatically triggered.

When the Promise status changes to Rejected, the onRejected callback is automatically triggered.

Once the state of Promise changes to a pity or Rejected, the state will not change again.

In other words, the Promise state change is irreversible.

Promise code examples:

const promise = new Promise(function(resolve, reject) {
	// Execute synchronously during Promise construction
  resolve(100)  // This is a big pity
  reject(new Error('Abnormal cause'))  // The Promise state changes to Rejected
})

promise.then(function (value) {
  // Successful callback function
  console.log('resolved', value)
}, function (error) {
  // Failed callback function
  console.log('rejected', error)
})
Copy the code

Note that only one of resolve and reject can be executed, because the state changes and the transition does not occur.

The ondepressing function will execute synchronously during Promise construction and will accept two parameters: resolve and Reject.

The main function of resolve after implementation is to change the state of the Promise into Fulfilled, which is also a pity. Also, we typically pass the result of an asynchronous task through the resolve parameter.

Reject changes the state of the Promise to Rejected, which is Rejected. At the same time, we generally pass exception objects for asynchronous tasks through reject. Note that the argument is an exception object: new Error(‘ cause of failure ‘).

After the Promise instance is created, we can use the then method of the instance to specify the onFulfilled and onRejected callback functions, which are the first and second parameters of the THEN method, respectively.

Also note that even if there is no synchronous operation logic in the Promise to execute, the callbacks specified by the THEN method will be queued until the current call stack is empty and then removed from the message queue for execution.

The Promise specification was first proposed by the CommonJS community. It was standardized in ES2015 as a language specification.

Promise Case – Simulate Ajax

const ajax = (url: string) = > {
    return new Promise((resolve, reject) = > {
        const xhr = new XMLHttpRequest()
        xhr.responseType = 'json'
        xhr.open('GET', url)
        xhr.onload = function () {
            console.log(this);
            if (this.status === 200) {
                resolve(this.response)
            } else {
                reject(new Error(this.statusText))
            }
        }
        xhr.send()
    })
}

ajax('./1-ajax.json').then(res= > {
    console.log('onFulfilled', res);
}).catch(error= > {
    console.log('onRejected', error);
})
Copy the code

On the surface

The essence of promises is to use callbacks to define tasks that need to be performed after an asynchronous task ends

This is a big pity, but the callback function here is passed in through the then method, and the Promise can be divided into two kinds of callback function, which is onFulfilled and onRejected.

Since promises still use callback functions, doesn’t the same problem occur when multiple tasks are initiated?

You might be thinking of code like this (there are still callback nesting issues

ajax('/api/url.json').then(function (url) {
    ajax('/api/url.json').then(function (url) {
        ajax('/api/url.json').then(function (url) {
            ajax('/api/url.json').then(function (url) {})})})Copy the code

If written this way, promises really don’t exist, not only do they not solve the problem, but they add complexity to the code.

The chain call to Promise

The big advantage of promises is that they can be invoked chained.

Chain calls are designed to keep asynchronous tasks as flat as possible, thus avoiding the problem of nested callbacks.

The Promise onRejected callback can be omitted.

The Promise then method is characterized by the fact that it also returns a Promise object internally.

const promiseObj = ajax('./1-ajax.json')
const promiseResult = promiseObj.then(res= > {
    console.log('onFulfilled', res);
}).catch(error= > {
    console.log('onRejected', error);
})
console.log(promiseResult)
console.log(promiseObj === promiseResult)
Copy the code

PromiseResult is also a Promise object. To be clear, promiseObj and promiseResult are completely different Promise objects.

The then method returns a brand new Promise object,

The goal is to implement a Promise chain so that each Promise object can be responsible for an asynchronous task without any impact on each other.

 ajax('./1-ajax.json').then(res= > {
    console.log(1);
}).then(res= > {
    console.log(2);
}).then(res= > {
    console.log(3);
}).then(res= > {
    console.log(4);
})
/ / 1
/ / 2
/ / 3
/ / 4
Copy the code

Each of these THEN methods is essentially adding a state-specific callback to the Promise object returned by the previous THEN

These promises are executed sequentially, so the callbacks in then are executed sequentially.

A Promise object can be manually returned inside the THEN.

ajax('/api/url.json').then(function (url) {
    return ajax('/api/url.json')
}).then(res= > {
    console.log(res);
    return 'foo'
}).then(value= > {
    console.log(value)
})
Copy the code

This allows us to do the above code like this: chain calls to avoid nested callbacks and to keep the code as flat as possible.

Also, if the then method returns a value instead of a Promise object, that value will be used as the value of the next Promise response.

If nothing is returned from the then method, a Promise object with a value of undefined is returned by default.

Conclusion:

  1. The then method of the Promise object returns a brand new Promise object, so we can use chained calls to avoid nested callback functions.
  2. The subsequent THEN method registers the callback for the Promise returned by the previous THEN.
  3. 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.
  4. If a Promise is returned in the callback, then callbacks to subsequent THEN methods wait for it to end.

Promise onRejected

The onRejected callback function will be called if an exception occurs during the Promise task execution.

ajax('/api/url.json').then(res= > {
    throw false
}, error= > {
    // onRejected
  	console.log('onRejected')
}).catch(error= > {
    // catch
  	console.log('catch')})Copy the code

Which line does the above code execute? The answer is 8.

ajax('/api/url.json').then(res= > {
    throw false
}).catch(error= > {
    console.log('rejected')
})

ajax('/api/url.json').then(res= > {
    throw false
}).then(() = > {
    console.log('fulfilled')},error= > {
    console.log('rejected')})Copy the code

The two notations above are equivalent.

That is, the catch method is simply an implementation of onRejected, the next then method.

So the code for the previous question could also be written like this:

ajax('/api/url.json').then(res= > {
    throw false
}, error= > {
    // onRejected
    console.log('onRejected')
}).then(() = > {
    console.log('fulfilled');
}, error= > {
    // catch
    console.log('catch')})Copy the code

Once the Promise state becomes a pity or Rejected, the state will not change again

Obviously, exceptions can only be caught by the next THEN method, so the previous answer was 8, and in this case line 10 is executed.

Thus, the catch method is actually adding an exception callback to the Promise object returned by the previous THEN method.

In addition, by looking at the following code, we can derive a Promise chain-call feature.

ajax('/api/url.json').then(res= > {
    console.log(1);
}).then(() = > {
    console.log(2);
}).then(() = > {
    throw false
}).then(() = > {
    console.log(4);
}).catch(error= > {
    console.log('rejected')})Copy the code

In the same Primise chain, the exceptions in the previous Promise will always be passed down.

So the catch catches the exception in line 6, the fourth Promise. There’s another distinction we need to make

The onRejected callback function, the second argument to the then method, can only catch exceptions for the current Promise.

Unhandledrejection Catches the Promise exception globally

Is not recommended. We should explicitly catch every possible exception in our code, not throw it at the global level.

// Web
window.addEventListener('unhandledrejection'.event= > {
    const { reason, promise } = event
    // Reason => The reason for the failure, usually an error object
    // PROMISE => Abnormal promise object
    console.log(reason, promise);
    event.preventDefault()
}, false)

// Node
process.on('unhandledRejection'.(reason, promise) = > {
    console.log(reason, promise);
})
Copy the code

Promise static methods: resolve, reject

Promise.resolve('foo').then(value= > {
    console.log(value)  // foo
})

new Promise(function (resolve, reject) {
    resolve('foo')
}).then(value= > {
    console.log(value)  // foo
})
Copy the code

Promise. Resolve can quickly convert a value to a Promise object.

Similarly, we wrap a Promise object with promise.resolve as shown in the following code, and the two results are essentially the same.

const promise_1 = ajax('./1-ajax.json')
const promise_2 = Promise.resolve(promise_1)

console.log(promise_1 === promise_2);  // true
Copy the code

Resolve can also pass promise. resolve an object containing the then attribute, using thenable’s pattern:

Promise.resolve({
    then: function (onFulfilled, onRejected) {
        onFulfilled('foo')
    }
}).then(value= > {
    console.log(value);
})
Copy the code

The same is true for the promise.reject method, which we won’t go into too much detail here:

Promise.reject(new Error('rejected')).catch(error= > {
    console.log(error)
})
Copy the code

Quickly create a failed Promise object with a Promise failure reason.

Promise parallel execution: promise. all, promise.race

Promise.all()

In general, promises are used as chained calls in the previous example

Each Promise object or then method is executed sequentially.

What if we want to execute multiple promises in parallel?

As is often the case with projects, we need to request multiple interfaces at the same time to get the data rendering page.

It would be much easier to use Promise’s all method.

const promiseAll = Promise.all([ajax('./1-ajax.json'), ajax('./1-ajax.json')])
promiseAll.then(res= > {
    console.log(res)
})
Copy the code

Promise.all will combine multiple promises into one Promise for unified management.

The new Promise returned by promise.all is completed when all internal promises have been completed.

The result of this Promise is an array containing the results of each asynchronous task execution.

Note that promiseAll is a failed outcome as long as there is one failed outcome among multiple promises managed.

Parallel requests consume less time than sequential requests.

Promise.race()

Promise.race, like promise. all, consolidates multiple promises into a brand new Promise management.

The difference is that promise.all waits for all tasks to complete, whereas promise.race only waits for the first task to complete.

const request = ajax('./1-ajax.json')
const timeout = new Promise((resolve, reject) = > {
    setTimeout(() = > reject(new Error('timeout')), 500)})Promise.race([request, timeout]).then(value= > {
    console.log(value)
}).catch(error= > {
    console.log(error)
})
Copy the code

Promise performs timing issues, macro tasks vs. micro tasks

console.log('global start');

setTimeout(() = >{
  console.log('setTimeout')},0)

Promise.resolve().then(() = > {
    console.log('promise')
}).then(() = > {
    console.log('promise 2')});console.log('global end');

// global start
// global end
// promise
// promise 2
Copy the code

What is a macro task? What are microtasks?

The tasks in the callback queue are called macro tasks. During macro task execution, some additional requirements can be temporarily added. These additional requirements can be handled in two ways:

  • The first option is to queue as a new macro task

  • The second type is microtasks that serve as current tasks.

Microtasks are performed immediately after the current task has finished.

Microtasks were added to JS as a late addition to improve overall responsiveness.

Which are macro tasks? Which are microtasks?

The Promise callback is executed as a microtask, so it is automatically executed at the end of this call. SetTimeout, on the other hand, enters the end of the callback queue in the form of a macro task.

Most asynchronous calls today are executed as macro tasks.

The Promise, MutationObserver, and Process. nextTick are executed as microtasks at the end of this round.