【JS基础】从JavaScript中的for...of说起(下) - async和await

简介: 【JS基础】从JavaScript中的for...of说起(下) - async和await

写在前面


在上一篇文章中,梳理了javascript中的两个重要概念:iterator和generator,并且介绍了两者在异步操作中的应用。


【JS基础】从JavaScript中的for...of说起(上) - iterator 和 generator

CoyPan,公众号:符合预期的CoyPan【JS基础】从JavaScript中的for...of说起(上) - iterator 和 generator


在异步操作中使用iterator和generator是一件比较费劲的事情,而ES2017给我们提供了更为简便的async和await。


async和await


01

async


mdn上说:async function 声明用于定义一个返回 AsyncFunction 对象的异步函数。异步函数是指通过事件循环异步执行的函数,它会通过一个隐式的 Promise 返回其结果。


简单来说,如果你在一个函数前面使用了async关键字,那么这个函数就会返回一个promise。如果你返回的不是一个promise,JavaScript也会自动把这个值"包装"成Promise的resolve值。例如:


// 返回一个promiseasync function aa() {    return new Promise(resolve => {        setTimeout(function(){            resolve('aaaaaa');        }, 1000);    });}
aa().then(res => {    console.log(res); // 1s后输出 'aaaaaa'});
Object.prototype.toString(aa) === '[object AsyncFuntion]'; // truetypeof aa === 'function'; // true
// 返回一个非promiseasync function a() {    return 1;}const b = a(); console.log(b); // Promise {<resolved>: 1}
a().then(res => {    console.log(res); // 1})


async 函数抛出异常时,Promise 的 reject 方法也会传递这个异常值。例如下面的例子:


async function a(){    return bbb;}
a().then(res => {    console.log(res);}).catch( e => {    console.log(e); // ReferenceError: bbb is not defined});


02

await


await  操作符用于等待一个Promise 对象。它只能在异步函数 async function 中使用。await 表达式会暂停当前 async function 的执行,等待 Promise 处理完成。若 Promise 正常处理(fulfilled),其回调的resolve函数参数作为 await 表达式的值,继续执行 async function。若 Promise 处理异常(rejected),await 表达式会把 Promise 的异常原因抛出。另外,如果 await 操作符后的表达式的值不是一个 Promise,则返回该值本身。看下面的例子:


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(); // 1s后,会输出1, 紧接着,会输出2
// 把await放在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();


当代码执行到await语句时,会暂停执行,直到await后面的promise正常处理。这和我们之前讲到的generator一样,可以让代码在某个地方中断。只不过,在generator中,我们需要手动写代码去执行generator,而await则是像一个自带执行器的generator。某种程度上,我们可以理解为:await就是generator的语法糖。看下面的代码:


const p = function() {    return new Promise(resolve, reject=>{        setTimeout(function(){            resolve(1);        }, 1000);    });};
const f = async function() {    const res = await p();    console.log(res);}


我们使用babel对这段代码进行转化,得到以下的代码:


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


通过变量名可以看到,babel也是将async await转换成了generator来进行处理的。


任务队列


以下的场景其实是很常见的:

我们有一堆任务,我们需要按照一定的顺序执行这一堆任务,拿到最终的结果。这里,把这一堆任务称为一个任务队列。

js中的队列其实就是一个数组。


01

同步任务队列


任务队列中的函数都是同步函数。这种情况比较简单,我们可以采用reduce很方便的遍历。


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

02

异步任务队列


任务队列中的函数都是异步函数。这里,我们假设所有的函数都是以Promise的形式封装的。现在,需要依次执行队列中的函数。假设异步任务队列如下:


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];


可以使用正常的for循环或者for...of... 来遍历数组,并且使用async await来执行代码(注:不要使用forEach,因为forEach不支持异步代码)


// for循环(async function(){    for(let i = 0; i < taskList.length; i++) {        await taskList[i]();    }})();
// for..of..(async function(){    for(let fn of taskList) {      await fn();  }})();


03

koa2洋葱模型实现原理

koa2,大家都不陌生了。koa2的洋葱模型,是怎么实现的呢?先来看下面的代码:


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);
// 访问node时,代码输出如下:// 1// 3// 5 // 4 // 2// GET / - 6ms


其实实现起来很简单,app.use就是将所有的回调函数都塞进了一个任务队列里面,调用await next()的时候,会直接执行队列里面下一个任务,直到下一个任务执行完成,才会接着执行后续的代码。我们来简单实现一下最基本的逻辑:


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();
// 输出: 1、3、fn1、5、7、6、4、2


写在后面


可以看到,使用async和await进行异步操作,可以使代码看起来更为清晰,简单。我们可以用同步代码的方式来书写异步代码。本文还探究了前端开发中很常见的任务队列的相关问题。通过本文和上一篇文章,我自己也对js中的异步操作有了更深入,更全面的认识。符合预期。

相关文章
|
11天前
|
JSON 前端开发 JavaScript
【JavaScript技术专栏】JavaScript异步编程:Promise、async/await解析
【4月更文挑战第30天】JavaScript中的异步编程通过Promise和async/await来解决回调地狱问题。Promise代表可能完成或拒绝的异步操作,有pending、fulfilled和rejected三种状态。它支持链式调用和Promise.all()、Promise.race()等方法。async/await是ES8引入的语法糖,允许异步代码以同步风格编写,提高可读性和可维护性。两者结合使用能更高效地处理非阻塞操作。
|
9天前
|
JSON JavaScript 前端开发
使用JavaScript和XLSX.js将数据导出为Excel文件
使用JavaScript和XLSX.js将数据导出为Excel文件
20 0
|
11天前
|
JavaScript 前端开发 开发工具
【JavaScript 技术专栏】Node.js 基础与实战
【4月更文挑战第30天】本文介绍了Node.js的基础及应用,包括事件驱动的非阻塞I/O、单线程模型和模块系统。内容涵盖Node.js的安装配置、核心模块(如http、fs、path)及实战应用,如Web服务器、文件操作和实时通信。文章还讨论了Node.js的优劣势、与其他技术的结合,并通过案例分析展示项目实施流程。总结来说,Node.js是高效后端开发工具,适合构建高并发应用,其广阔的应用前景值得开发者探索。
|
11天前
|
JSON JavaScript 前端开发
深入探讨javascript的流程控制与分支结构,以及js的函数
深入探讨javascript的流程控制与分支结构,以及js的函数
|
16天前
|
前端开发 JavaScript
在JavaScript中,回调函数、Promise和async/await这三种异步处理机制的优缺点
JavaScript的异步处理包括回调函数、Promise和async/await。回调函数简单易懂,但可能导致回调地狱和错误处理困难。Promise通过链式调用改善了这一情况,但仍有回调函数需求和学习成本。async/await提供同步风格代码,增强可读性和错误处理,但需ES8支持,不适用于并发执行。根据项目需求选择合适机制。
|
17天前
|
JavaScript 前端开发 算法
< JavaScript小技巧:如何优雅的用【一行代码 】实现Js中的常用功能 >
在开发中,采用简洁的语法和结构,遵循一致的命名规范,具有良好的代码组织和注释,能很好的提高代码的质量。可读性:易于阅读和理解。清晰的命名、简洁的语法和良好的代码结构可以使代码的意图更加明确,降低理解代码的难度,提高代码的可读性。可维护性:易于维护。当代码逻辑清晰、结构简洁时,开发者可以更快速地定位和修复bug,进行功能扩展或修改。同时,可读性高的代码也有助于后续的代码重构和优化。可扩展性:更具有扩展性和灵活性。清晰的代码结构和简洁的代码风格使得添加新功能、修改现有功能或扩展代码更加容易。
< JavaScript小技巧:如何优雅的用【一行代码 】实现Js中的常用功能 >
|
18天前
|
JavaScript 前端开发
js开发:请解释this关键字在JavaScript中的用法。
【4月更文挑战第23天】JavaScript的this关键字根据执行环境指向不同对象:全局中指向全局对象(如window),普通函数中默认指向全局对象,作为方法调用时指向调用对象;构造函数中指向新实例,箭头函数继承所在上下文的this。可通过call、apply、bind方法显式改变this指向。
8 1
|
19天前
|
前端开发 JavaScript 编译器
深入解析JavaScript中的异步编程:Promises与async/await的使用与原理
【4月更文挑战第22天】本文深入解析JavaScript异步编程,重点讨论Promises和async/await。Promises用于管理异步操作,有pending、fulfilled和rejected三种状态。通过.then()和.catch()处理结果,但可能导致回调地狱。async/await是ES2017的语法糖,使异步编程更直观,类似同步代码,通过事件循环和微任务队列实现。两者各有优势,适用于不同场景,能有效提升代码可读性和维护性。
|
11天前
|
存储 移动开发 JavaScript
学习javascript,前端知识精讲,助力你轻松掌握
学习javascript,前端知识精讲,助力你轻松掌握
|
18天前
|
JavaScript 前端开发 测试技术
学习JavaScript
【4月更文挑战第23天】学习JavaScript
13 1