Writing in the front

This article was originally published on the public account CoyPan as expected.

In the last article, I combed through two important concepts in javascript: iterator and generator, and introduced the use of both in asynchronous operations.

From JavaScript for… Speaking of (1) – Iterator and generator

While using Iterators and generators in asynchronous operations is a bit of a chore, ES2017 gives us much easier async and await.

Async and await

async

The async function declaration is used to define an asynchronous function that returns an AsyncFunction object. An asynchronous function is a function that executes asynchronously through an event loop and returns its result with an implicit Promise.

Simply put, if you use the async keyword in front of a function, the function returns a promise. If you return something other than a promise, JavaScript will automatically “wrap” that value into the promise’s resolve value. Such as:

// 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
});

typeof aa === 'function'; // true
Object.prototype.toString(aa) === '[object AsyncFunction]'; // true
Object.prototype.toString(aa()) === '[object Promise]'; // true



// Return a non-promise
async function a() {
    return 1;
}
const b = a(); 
console.log(b); // Promise {<resolved>: 1}

a().then(res= > {
    console.log(res); / / 1
})
Copy the code

When async throws an exception, Promise’s Reject method also passes the exception. For example:

async function a(){
    return bbb;
}

a()
.then(res= > {
    console.log(res);
})
.catch( e= > {
    console.log(e); // ReferenceError: bbb is not defined
});
Copy the code

await

The await operator is used to wait for a Promise object. It can only be used in async function. Await expression suspends execution of the current async function until the Promise processing completes. If the Promise is fulfilled normally, the resolve function parameter of its callback will be the value of the await expression and the async function will continue to be executed. If a Promise handles an exception (Rejected), the await expression throws the Promise’s exception reason. Also, if the value of the expression after the await operator is not a Promise, the value itself is returned. Look at the following example:

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, 1 will be printed, followed by 2


// put await in a try catch
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();Copy the code

When code executes into an await statement, execution is paused until the promise following the await statement is processed properly. This, like generator, allows code to break somewhere. Except that in generator we need to write code manually to execute generator and await is like a generator with its own executor. To some extent we can understand that await is the syntactic 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);
}
Copy the code

We use Babel to transform this code to produce 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); }; } ();Copy the code

As you can see from the variable name, Babel also converts async await to generator for processing.

Task queue

The following scenario is actually quite common:

We have a bunch of tasks, and we need to do a bunch of tasks in a certain order to get the final result. In this case, the stack of tasks is called a task queue.

A queue in JS is just an array.

Synchronizing task queue

The functions in the task queue are synchronization functions. This case is relatively simple, and we can use Reduce for 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
Copy the code

Asynchronous task queue

The functions in the task queue are asynchronous functions. Here, we assume that all functions are wrapped in the form of promises. Now you need to execute the functions in the queue in turn. Suppose the asynchronous task queue looks like this:

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];
Copy the code

You can use normal for loops or for… of… To iterate over groups of numbers and execute code with async await (note: do not use forEach, forEach does not support this scenario)

/ / a for loop
(async function(){
    for(let i = 0; i < taskList.length; i++) {
        await taskList[i]();
    }
})();

// for.. of..
(async function(){
    for(let fn of taskList) {
    	await fn();
	}
})();

Copy the code

Koa2 Onion model implementation principle

Koa2, you’re all familiar with it. How does koA2’s Onion model work? 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
Copy the code

App. use is to stuff all callback functions into a task queue, call await next(), and execute the next task in the queue until the next task is finished. Let’s implement the basic logic briefly:

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();

// Outputs: 1, 3, fn1, 5, 7, 6, 4, 2
Copy the code

Write in the back

As you can see, asynchronous operations with async and await make the code look cleaner and simpler. We can write asynchronous code as synchronous code. This article also explores issues related to task queues, which are common in front-end development. Through this article and the last one, I myself have a deeper and more comprehensive understanding of asynchronous operations in JS. As expected.