One article lets you master Promise, async, await
JS series 9.0 introduces the use of promise, async and await in detail, including the handwritten static method and extension method of promise; in addition, it also introduces the conversion between async and promise.
1. Concept
Promise is a solution to asynchronous programming
, which can be used to solve the problem of callback hell in Ajax
Promise is a built-in constructor. When using it, you need to use the new
keyword to create a Promise object, and you need to pass in a callback function (called executor)
A Promise must be in one of the following states:
Pending: The initial state, neither fulfilled nor rejected, for example, a network request is in progress, or the timer has not expired
Fulfilled : it is in this state when we actively call the resolve
callback function, which means that the operation is successfully completed, and the callback function in .then()
will be executed
Rejected: It is in this state when we actively call the reject
callback function, which means that the operation failed, and the callback function in .catch()
will be executed
The state of Promise can only be changed once, and there are only two changes: pending => fulfilled; pending => rejected
(that is, only promises in the pending state can become successful or failed, and they are already in the successful/failed state The promise will no longer change to failure/success, and once the state of the Promise is determined, it cannot be changed (locked)
2. Use of Promise
2.1 Basic usage rules
//Create a promise instance
var p = new Promise((resolve, reject) => {
//When resolve and reject exist at the same time, which callback is executed first
resolve('success');
reject('error');
});
//then method passes in two callback functions, the first callback function will be called back when Promise executes the resolve function
// The second callback function will be called back when Promise executes the reject function
p.then(
(res) => {
console.log('success', res);
},
(err) => {
console.log('Failed', err);
},
);
2.2 Timer-based asynchronous
Promise is often placed in a function and returned out
function timeFoo(data){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
if(data==='success')
resolve('successful parameters')
else reject('error')
},1000)
})
}
timeFoo('success').then(
res=>console.log('success', res),
err=>console.log('failure', err)
)
console.log('I am synchronous and will execute first');
2.3 Use promise to encapsulate ajax
const promiseAjax = function (method, url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr. readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
//Callback when the request is successful
resolve(JSON. parse(xhr. responseText));
//Call reject when the request fails
} else reject('error:' + xhr.status);
}
};
xhr.open(method,url)
xhr.send()
});
};
let test= promiseAjax('get','http://www.filltext.com/?rows=2&fname={firstName}&lname={lastName}')
test.then(res=>{
console.log(res);
},err=>{
console.log(err);
})
2.4 resolve parameter problem
Promise.resolve(value)
: returns a Promise whose state is determined by the given value
Promise.reject(reason)
: returns a Promise object with state o and passes it to the failure handler function
-
If the parameter passed in by resolve is an ordinary value or a non-Promise type object, the returned result is a successful promise object, and the passed in parameter will be used as the parameter of the then callback;
Promise.resolve('123').then((data) => console.log(data)); let obj = { name: 'zhang', age: '18', }; Promise.resolve(obj).then((data) => console.log(data));
-
If another Promise is passed in resolve, the new Promise will determine the state of the original Promise
Promise. resolve( new Promise((resolve,reject)=>{ //resolve('success') reject('failed') }) ).then( (data) => console. log(data), (err)=>console.log(err) ) //fail
-
If an object with a then method is passed in to resolve, the then method will be executed and the state of the Promise will be determined according to the result of the then method:
let obj2={ then:(resolve,reject)=>{ resolve('successful') reject('failed') } } Promise.resolve(obj2).then( (data) => console. log(data), (err)=>console.log(err) )//successful
PS:No matter what form the parameters passed in by Promise.reject are, they will be directly passed to the catch as parameters of the reject state
2.5 proimse chain call to solve callback hell
The .then() method takes two parameters, the first parameter is the callback function that handles the cashed state, and the second parameter is the callback function that handles the rejected state; each .then() method also Returns a newly generated promise object, this object can be used as a chain call
When there is no function in .then() that can return a promise object, the chain call will directly continue to the next ring operation. Therefore, the chain call can omit all the callback functions that handle the rejected status before the last .catch()
That is, the chain call can pass in a successful callback function through .then(), and finally use .catch() to handle the failure (also known as exception penetration)
.finally(): Regardless of success or failure, it will be executed at the end
//Timer simulates callback hell
//Print 1, 2, 3, 4 every second
setTimeout(() => {
console. log(1);
setTimeout(() => {
console. log(2);
setTimeout(() => {
console. log(3);
setTimeout(() => {
console. log(4);
}, 1000);
}, 1000);
}, 1000);
}, 1000);
promise.then solves callback hell
let logNumber = function (data) {
return new Promise((resolve, rejcet) => {
setTimeout(() => {
resolve(console.log(data));
rejcet('error');
}, 1000);
});
};
logNumber(1)
.then(() => {
return logNumber(2);
})
.then(() => {
return logNumber(3);
})
.then(() => {
return logNumber(4);
});
The .catch method will also return a Promise object
, so you can continue to call the then method or the catch method after the catch method;
If you want to break the promise chain so that the request will not continue after the request fails, you need to throw an exception
(Break the promise chain: return new Promise(() => {})
but using this method will not execute.finally)
logNumber(1)
.then(() => {
return logNumber(2);
})
.then(() => {
return Promise.reject('failure, catch with catch');
})
.catch((err) => {
console. log(err);
})
.then(() => {
// catch returns a promise object, and the parameter is undefined, execute resolve
return logNumber(3);
})
.finally(() => {
return logNumber('Whether it succeeds or fails, it will show that the printing is complete');
});
Result: After executing .catch, the subsequent .then will be executed and 3 will be printed
If an error is thrown after the catch: then the then after the catch will not be executed, and the promise chain will be interrupted
.catch((err) => {
console.log(err);
throw new Error('error message')
})
Results:
Note: usually put the catch at the end of all then to handle the failure. At this time, the then after the promise failure state will not continue to execute
logNumber(1)
.then(() => {
return logNumber(2);
})
.then(() => {
return Promise.reject('failure, catch with catch');
})
//3 will not print
.then(() => {
return logNumber(3);
})
.catch((err) => {
console. log(err);
})
//Only print 1 2 and failure information
3. Static method of Promise
3.1 Promise. all(iterable)
iterable:
an iterable object, such as Array or String, Array, Map, Set all belong to the iterable type of ES6
This method returns a new promise
, which succeeds only when all promises are successful and will form an array of the return values of all promises; when a promise state is reject, the new promise state is reject, and will Use the return value of the first reject as a parameter
If the parameter contains non-promise values, these values will be ignored, but will still be placed in the return array (if promise.all succeeds)
function logNumber(data, flag, delay) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (flag) resolve(data);
else reject('Failed');
}, delay);
});
}
let p1 = logNumber(1, true, 1000);
let p2 = logNumber(2, true, 1000);
//let p2 = logNumber(2,false,1000)
//If p2 is changed to false, return failure
let p3 = logNumber(3, true, 3000);
Promise.all([p1, '123', p2, p3]).then(
(res) => console. log(res),
(err) => console. log(err),
); //return [1, '123', 2, 3] after 3 seconds
Handwritten promise.all
Promise. myAll = function (promises) {
//The return result is a promise object
return new Promise((resolve, reject) => {
let result = [];
promises. forEach((val) => {
val.then(
//If successful, store the result in an array, and when the number of successes is equal to the number of promises passed in, change the status of the returned promise to success and return the result
(res) => {
result. push(res);
if (result. length === promises. length) {
resolve(result);
}
},
(err) => {
//If there is a failure, change the state to failure
reject(err);
},
);
});
});
};
3.2 Promise. allSettled(iterable)
iterable: an iterable object, such as Array, where each member is a Promise
This method will only have a final state when all Promises have results (whether they are fulfilled or rejected); returns a new Promise with a successful state
and an array of objects, each representing the corresponding promise result (result contains status and corresponding value)
It is usually used when you have multiple async tasks that do not depend on each other to complete successfully, or when you always want to know the result of each promise
Handwritten promise.allSettled
The idea is basically the same as the handwritten promise.all, except that the stored result should be an array object
Promise. myAllSettled = function (promises) {
//The return result is a promise object
return new Promise((resolve, reject) => {
let result = [];
promises. forEach((val) => {
val.then(
//Store the state and value of each promise into an array
(res) => {
result.push({status:'fulfilled',value:res});
if (result. length === promises. length) {
resolve(result);
}
},
(err) => {
result.push({status:'rejected',value:err});
if (result. length === promises. length) {
resolve(result);
}
},
);
});
});
};
3.3 Promise. any(iterable)
Promise.any() takes an iterable of Promises, and as long as one of the promises succeeds, it returns the promise that has succeeded. If all promises fail, return a failed promise
Handwritten promise.any
Promise. myAny = function (promises) {
//The return result is a promise object
return new Promise((resolve, reject) => {
const reasons = [];
promises. forEach((promise) => {
promise. then(
(res) => resolve(res),
(err) => {
reasons. push(err);
//If all statuses are failure, return failure function
if (reasons. length === promises. length) {
reject(new AggregateError(reasons));
}
},
);
});
});
};
3.4 Promise. race(iterable)
The Promise.race(iterable) method returns a promise. Once a promise in the iterator succeeds or rejects, the returned promise will succeed or reject, while any must return a successful.
Promise. myRace = function (promises) {
//The return result is a promise object
return new Promise((resolve, reject) => {
promises. forEach((promise) => {
promise. then(
(res) => resolve(res),
(err) => reject(err),
);
});
});
};
4. Promise extension method
4.1 Implement a Promise scheduler with parallel constraints
// Implement a Promise scheduler with parallel constraints
function Scheduler(limit) {
this.queue = [];
this.maxCount = limit;
this. runCounts = 0;
}
Scheduler.prototype.add = function (promiseCreator) {
//promiseCreator is a promise instance object
this.queue.push(promiseCreator);
};
Scheduler.prototype.request = function () {
if (
!this.queue||
!this.queue.length||
this.runCounts >= this.maxCount
)
return;
this. runCounts++;
//First in, first out, use shift to pop up
this.queue
.shift()()
.then(() => {
//Decrement the number of execution queues by 1, and resend the request
this. runCounts--;
this. request();
});
};
Scheduler.prototype.taskStart = function () {
//If i is less than the limit number, make a request
for (let i = 0; i < this.maxCount; i++) {
this. request();
}
};
// use test
const scheduler = new Scheduler(3);
const addTask = (time, order) => {
scheduler. add(function () {
return new Promise((resolve) => {
setTimeout(resolve, time);
}).then(() => console.log(order));
});
};
addTask(1000, '1');
addTask(500, '2');
addTask(300, '3');
addTask(400, '4');
scheduler. taskStart();
//3 2 4 1
4.2 Use Promise to realize asynchronous loading of pictures
let asyncImg = (url) => {
return new Promise((resolve,reject)=>{
let img = new Image()
img.src = url
img.onload = () => {
resolve(img);
};
img.onerror = (err) => {
console.log(`failure, general operation failed here`);
reject(err);
};
})
}
//Use: load multiple images asynchronously
let imglist = ['../imgs/run1.jpg', '../imgs/run2.jpg', '../imgs/run3.jpg'];
//The parameter imglist passed to Promise.all does not contain any promises, then an asynchronous completion is returned, and the return value is the passed array imglist
imglist.forEach((imgsrc) => {
// Pass the image path to the asynchronous loading function
asyncImg(imgsrc).then((res) => {
setTimeout(() => {
document.body.append(res);
}, 800);
})
.catch(err=>console.log(err))
});
// Load a picture asynchronously
asyncImg('../imgs/run1.jpg')
.then((res) => {
console.log('loaded successfully');
document.body.append(res)
})
.catch((error) => {
console.log('Failed to load');
});
5. async and await
5.1 Concept
The emergence of async and await is to simplify the chain call of promise, making asynchronous code look more like synchronous code and easier to read
async function return value:
A Promise
, the result of this promise object is determined by the return value of the async function execution: it will either be resolved by a value returned by the async function, or it will be rejected by an exception thrown from the async function
async function foo() {
console.log('foo function start');
// 1. Return a value
//return 2
// 2. Return thenable
return {
then: function (resolve, reject) {
reject('failed'); // capture with catch
},
};
// 3. Return Promise
// return new Promise((resolve, reject) => {
// setTimeout(() => {
// resolve("Successful promise")
// }, 2000)
// })
// 4. The exception in the asynchronous function will be rejected as the Promise returned by the asynchronous function
// throw new Error("error message")
}
// The return value of an asynchronous function must be a Promise
const promise = foo();
promise
.then((res) => {console. log( res);})
.catch((res) => console.log(res));
await:
The expression on the right side of await is usually a promise object, but it can also be other values
If the expression is a promise object, await returns the promise success value
If the expression is another value, directly use this value as the return value of await
If the Promise returned by the expression after await is reject, then the result of this rejection will be directly used as the Promise of the function
reject value
PS:
await must be written in the async function, but there can be no await in the async functionThe function body of an async function can be seen as separated by zero or more await expressions. The first line of code up to (and including) the first await expression (if any) runs synchronously. In this way, an async function without an await expression will run synchronously. However, if there is an await expression in the body of the function, the async function must be executed asynchronously
As far as I understand, simply speaking, if the await keyword is not used in the async function, it will be treated as a normal function and executed in the order of js execution from top to bottom;
If there is an await keyword in async, the statement after the first await expression will be put into the microtask queue for asynchronous execution
, and when the second await area is reached, the process of the async function will be suspended again, and the awaited statement will continue put into microtask queue
<script>
console.log('synchronization task 1');
async function test() {
console. log(1);
console. log(2);
console. log(3);
}
let result = test();
console.log('synchronization task 2');
</script>
Without await:
console.log('synchronization task 1');
async function test() {
console. log(1);
await console. log(2);
console. log(3);
await console. log(4);
}
let result = test();
console.log('synchronization task 2');
If await is used, the expression after the first await will be put into the microtask queue:
5.2 Use async function to rewrite promise chain call
Promise chain calls:
function getData(url, data) {
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = function () {
resolve(data + this. responseText);
};
xhr. send();
});
}
getData(
'http://www.filltext.com/?rows=1&fname={firstName}',
'Start sending request',
)
.then((res) => {
return getData(
'http://www.filltext.com/?rows=1&lname={lastName}',
res,
);
})
.then((res) => {
return getData(
'http://www.filltext.com/?rows=1&company={business}',
res,
);
})
.then((res) => {
console. log(res);
})
.catch((e) => console.log('Something went wrong:' + e));
Rewrite with async and await (pass the result obtained each time as a parameter to the next request):
async function asyncSend() {
try {
let res1 = await getData(
'http://www.filltext.com/?rows=1&fname={firstName}',
'Start sending request',
);
let res2 = await getData(
'http://www.filltext.com/?rows=1&lname={lastName}',
res1,
);
let res3 = await getData(
'http://www.filltext.com/?rows=1&company={business}',
res2,
);
console.log('final result:' + res3);
} catch (e) {
console.log('An error occurred:' + e);
}
}
asyncSend();