Written in front
This article was first published in the Public No. [CoyPan, which meets expectations]
In the last article, I combed two important concepts in javascript: iterator and generator, and introduced their application in asynchronous operation.
[JS Foundation] Starting with for...of in JavaScript (Part I) - iterator and generator
Using iterator and generator in asynchronous operation is a difficult task, while ES2017 provides us with more convenient async and await.
async and await
async
mdn says: the async function declaration is used to define a return AsyncFunction Object's asynchronous function. An asynchronous function is a function that is executed asynchronously through an event loop, which passes through an implicit Promise Returns the result.
Simply put, if you use the async keyword in front of a function, the function returns a promise. If you do not return a promise, JavaScript will automatically "wrap" the value into a Promise resolve value. For example:
// Return a promise async function aa() { return new Promise(resolve => { setTimeout(function(){ resolve('aaaaaa'); }, 1000); }); } aa().then(res => { console.log(res); // Output'aaaaaa'after 1s }); Object.prototype.toString(aa) === '[object Object]'; // true typeof aa === 'function'; // true // Returns a Non-promise async function a() { return 1; } const b = a(); console.log(b); // PromiseĀ {<resolved>: 1} a().then(res => { console.log(res); // 1 })
When the async function throws an exception, Promise's reject method also passes the exception value. For example, the following example:
async function a(){ return bbb; } a() .then(res => { console.log(res); }) .catch( e => { console.log(e); // ReferenceError: bbb is not defined });
await
The await operator is used to wait for one Promise Object. It can only be used in asynchronous functions. async function Used in. The await expression pauses the current async function Execution, waiting for Promise processing to complete. If Promise is fulfilled, its callback resolve function parameter is used as the value of the await expression to continue execution async function . If Promise handles rejected exceptions, the await expression throws the cause of Promise's exceptions. In addition, if the value of the expression after the await operator is not a Promise, the value itself is returned. Look at the following examples:
const p = function() { return new Promise(resolve => { setTimeout(function(){ resolve(1); }, 1000); }); }; const fn = async function() { const res = await p(); console.log(res); const res2 = await 2; console.log(res2); }; fn(); // After 1s, it will output 1, and then, it will output 2. // Place await in try catch to catch errors const p2 = function() { return new Promise(resolve => { console.log(ppp); resolve(); }); }; const fn2 = async function() { try { await p2(); } catch (e) { console.log(e); // ppp is not defined } }; fn2();
When the code executes to the await statement, execution is paused until promise s after await are processed properly. This, like the generator we talked about earlier, allows code to break somewhere. However, in generator, we need to write code manually to execute generator, while await is like a generator with its own executor. To some extent, we can understand that await is the grammatical sugar of generator. Look at the following code:
const p = function() { return new Promise(resolve, reject=>{ setTimeout(function(){ resolve(1); }, 1000); }); }; const f = async function() { const res = await p(); console.log(res); }
We use babel to transform this code to get the following code:
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } var p = function p() { return new Promise(resolve, function (reject) { setTimeout(function () { resolve(1); }, 1000); }); }; var f = function () { var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() { var res; return regeneratorRuntime.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: _context.next = 2; return p(); case 2: res = _context.sent; console.log(res); case 4: case "end": return _context.stop(); } } }, _callee, this); })); return function f() { return _ref.apply(this, arguments); }; }();
As you can see from the variable name, babel also converts async await to generator for processing.
Task queue
The following scenarios are actually quite common:
We have a bunch of tasks that we need to perform in a certain order to get the final results. Here, we call this bunch of tasks a task queue.
The queue in js is actually an array.
Synchronized task queue
The functions in the task queue are synchronization functions. This situation is relatively simple, we can adopt reduce very convenient traversal.
const fn1 = function(i) { return i + 1; }; const fn2 = function(i) { return i * 2; }; const fn3 = function(i) { return i * 100; }; const taskList = [fn1, fn2, fn3]; let a = 1; const res = taskList.reduce((sum, fn) => { sum = fn(sum); return sum; }, a); console.log(res); // 400
Asynchronous task queue
The functions in the task queue are all asynchronous functions. Here, we assume that all functions are encapsulated in Promise form. Now, you need to execute the functions in the queue in turn. Assume that the asynchronous task queue is as follows:
const fn1 = function() { return new Promise( resolve => { setTimeout(function(){ console.log('fn1'); resolve(); }, 2000); }); }; const fn2 = function() { return new Promise( resolve => { setTimeout(function(){ console.log('fn2'); resolve(); }, 1000); }); }; const fn3 = function() { console.log('fn3'); return Promise.resolve(1); }; const taskList = [fn1, fn2, fn3];
You can traverse the array using a normal for loop or for...of... and use async await to execute the code (Note: Do not use forEach, because forEach does not support asynchronous code)
// for cycle (async function(){ for(let i = 0; i < taskList.length; i++) { await taskList[i](); } })(); // for..of.. (async function(){ for(let fn of taskList) { await fn(); } })();
Principle of realizing koa 2 onion model
Koa2, everyone is no stranger. How is the onion model of koa2 implemented? Let's start with the following code:
const Koa = require('koa'); const app = new Koa(); // logger app.use(async (ctx, next) => { console.log(1); await next(); console.log(2); const rt = ctx.response.get('X-Response-Time'); console.log(`${ctx.method} ${ctx.url} - ${rt}`); }); // x-response-time app.use(async (ctx, next) => { console.log(3); const start = Date.now(); await next(); console.log(4); const ms = Date.now() - start; ctx.set('X-Response-Time', `${ms}ms`); }); // response app.use(async ctx => { console.log(5); ctx.body = 'Hello World'; }); app.listen(3000); // When accessing node, the code output is as follows: // 1 // 3 // 5 // 4 // 2 // GET / - 6ms
In fact, it is very simple to implement. app.use is to insert all callback functions into a task queue. When await next() is called, the next task in the queue will be executed directly, and the subsequent code will not be executed until the next task is completed. Let's simply implement the basic logic:
class TaskList { constructor(){ this.list = []; } use(fn) { fn && this.list.push(fn); } start() { const self = this; let idx = -1; const exec = function() { idx++; const fn = self.list[idx]; if(!fn) { return Promise.resolve(); } return Promise.resolve(fn(exec)) } exec(); } } const test1 = function() { return new Promise( resolve => { setTimeout(function(){ console.log('fn1'); resolve(); }, 2000); }); }; const taskList = new TaskList(); taskList.use(async next => { console.log(1); await next(); console.log(2); }); taskList.use(async next => { console.log(3); await test1(); await next(); console.log(4); }); taskList.use(async next => { console.log(5); await next(); console.log(6); }); taskList.use(async next => { console.log(7); }); taskList.start(); // Output: 1, 3, fn1, 5, 7, 6, 4, 2
Written in the back
As you can see, using async and await for asynchronous operation can make the code look clearer and simpler. We can write asynchronous code in the form of synchronous code. This paper also explores the related issues of task queues which are very common in front-end development. Through this article and the previous article, I also have a deeper and more comprehensive understanding of asynchronous operation in js. It meets expectations.