资讯专栏INFORMATION COLUMN

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

hufeng / 1203人阅读

摘要:基础从中的说起上和在异步操作中使用和是一件比较费劲的事情,而给我们提供了更为简便的和。表达式会暂停当前的执行,等待处理完成。若正常处理,其回调的函数参数作为表达式的值,继续执行。若处理异常,表达式会把的异常原因抛出。

写在前面
本文首发于公众号:【符合预期的CoyPan】

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

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

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

async和await async

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

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

// 返回一个promise
async function aa() {
    return new Promise(resolve => {
        setTimeout(function(){
            resolve("aaaaaa");
        }, 1000);
    });
}

aa().then(res => {
    console.log(res); // 1s后输出 "aaaaaa"
});

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



// 返回一个非promise
async function a() {
    return 1;
}
const b = a(); 
console.log(b); // Promise {: 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
});
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中的队列其实就是一个数组。

同步任务队列

任务队列中的函数都是同步函数。这种情况比较简单,我们可以采用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
异步任务队列

任务队列中的函数都是异步函数。这里,我们假设所有的函数都是以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();
    }
})();
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中的异步操作有了更深入,更全面的认识。符合预期。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/103557.html

相关文章

  • JS基础JavaScript中的for...of说起(上) - iterator gene

    摘要:当这个迭代器的方法被首次后续调用时,其内的语句会执行到第一个后续出现的位置为止,后紧跟迭代器要返回的值。在这个回调函数里,我们使用第一个请求返回的,再次发起一个请求。 写在前面 本文首发于公众号:符合预期的CoyPan 后续文章:【JS基础】从JavaScript中的for...of说起(下) - async和await 先来看一段很常见的代码: const arr = [1, 2, ...

    wslongchen 评论0 收藏0
  • JavaScript 的 4 种数组遍历方法: for VS forEach() VS for/in

    摘要:对于,除非使用箭头函数,它的回调函数的将会变化。使用测试下面的代码,结果如下打印打印要点使用的规则要求所有回调函数必须使用箭头函数。 译者按: JS 骚操作。 原文:For vs forEach() vs for/in vs for/of in JavaScript 译者: Fundebug 本文采用意译,版权归原作者所有 我们有多种方法来遍历 JavaScript 的数组或者...

    joyqi 评论0 收藏0
  • JavaScript异步编程:Generator与Async

    摘要:从开始,就在引入新功能,来帮助更简单的方法来处理异步编程,帮助我们远离回调地狱。而则是为了更简洁的使用而提出的语法,相比这种的实现方式,更为专注,生来就是为了处理异步编程。 从Promise开始,JavaScript就在引入新功能,来帮助更简单的方法来处理异步编程,帮助我们远离回调地狱。 Promise是下边要讲的Generator/yield与async/await的基础,希望你已...

    leon 评论0 收藏0
  • ES2018 新特征之:异步迭代器 for-await-of

    摘要:不幸的是,迭代器不能用来表示这样的数据源。即使是的迭代器也是不够的,因为它的是异步的,但是迭代器需要同步确定状态。异步迭代器一个异步迭代器就像一个迭代器,除了它的方法返回一个的。 ES2018 新特性 异步迭代器(本文) 正则表达式反向(lookbehind)断言 正则表达式 Unicode 转义 非转义序列的模板字符串 正则表达式 s/dotAll 模式 正则表达式命名捕获组 对...

    klivitamJ 评论0 收藏0
  • JavaScript基础——深入学习async/await

    摘要:等待的基本语法该关键字的的意思就是让编译器等待并返回结果。这里并不会占用资源,因为引擎可以同时执行其他任务其他脚本或处理事件。接下来,我们写一个火箭发射场景的小例子不是真的发射火箭 本文由云+社区发表 本篇文章,小编将和大家一起学习异步编程的未来——async/await,它会打破你对上篇文章Promise的认知,竟然异步代码还能这么写! 但是别太得意,你需要深入理解Promise后,...

    张金宝 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<