资讯专栏INFORMATION COLUMN

Javascript 从异步函数到 Promise 到 Async/Await

yy736044583 / 365人阅读

摘要:实例执行以后,可以用方法分别指定状态和状态的回调函数。所以在使用处其中,函数接受回调函数,作为对象的状态变为时调用而回调函数作为对象的状态变为时调用。

我最近正在看的一本书《聊聊架构》,在进入今天的主题之前,我想和大家分享这本书里的一个概念“生命周期”。

大致是这么说的:

人类的生命很短,百年也只有短短的三万六千天。大部分人都不愿意接受一切都将消逝的事实,总想活得更久,占有更多,享受更多。在人们短短的一生中,如何延长自身的生命呢?一个办法就是尽可能做出更多的成就,能够让更多的人生活得更好。在同样的时间内创造出更多的产出,相当于把自己的生命延长了。

其中有效的做法就是将每次活动进行拆分,自己执行核心的【生命周期】,将【非核心生命周期】交给其它主体进行。

比如,用户购物这一场景,从用户进入到商店,进行浏览、询问、购买等活动,到离开商店,都是按时间顺序一步一步在执行的。对于我们来说,这其中的每一步都需要时间,对于现代人来说,太奢侈了。如果这时候,我们把这个购物行为进行拆分,把选购交由别人执行,由别人代替用户上街选择和过滤,或者通过网上推荐等来完成选购,用户只需要最后确定物品的挑选即可。用户的目的是买到自己需要的东西,而不是选购本身。这样就可以大大节省用户的时间,将更多的精力放到其他核心事情上。

再比如,现在吃饭,需要自己到菜市场买菜、回来煮饭煮菜,吃完饭后,还需要自己洗碗等。有时候完全可以叫外卖的,肚子饿了,点点【饿了吗】,接着就可以继续做自己的事情了,坐等外卖送到。将【煮饭】这一耗时的事情交给别人来做。

在编程语言中,这就是同步与异步的区别。异步的作用就是将耗时的事情交给【别人】来做,自己继续进行;当【别人】做完事情后,执行回调函数,带回结果,交回自己执行。

回调函数

虽然 Javascipt 语言是“单线程”执行环境,但在执行模式下,分成同步和异步两种模式,其中我们更多的使用回调函数的方式来进行异步操作,如:

blogs.search = (words, res) => {
    const titleQuery = new AV.Query(Blog)
    titleQuery.contains("title", words);

    const descQuery = new AV.Query(Blog)
    descQuery.contains("desc", words);

    const tagsQuery = new AV.Query(Blog)
    tagsQuery.contains("tags", words);

    const wordsQuery = AV.Query.or(titleQuery, descQuery, tagsQuery);

    wordsQuery.descending("createdAt");
    wordsQuery.limit(5);
    wordsQuery.find().then(function (results) {
        res(results);
    }, function (error) {
        res([]);
    });
}

这函数的作用就是通过关键词words搜索,获取满足条件的公众号文章,如果找不到就返回空数组。其中这里的res就是一个回调函数。在使用处:

server.route({
        method: "POST",
        path: "/searchblog",
        handler: function (request, reply) {
            const words = request.payload.words;
            ModelBlog.search(words, results => {
                let data = [];
                results.forEach(function(v) {
                    let wrap = {};

                    wrap.title = v.get("title");
                    wrap.description = v.get("desc");
                    wrap.picurl = v.get("picurl");
                    wrap.url = v.get("url");

                    data.push(wrap);
                });
                reply(data);
            });
        },
        config: {
            validate: {
                payload: {
                    words: Joi.string().required()
                }
            }
        }
    });

同样的,在handler函数中的reply也是一个回调函数,先通过ModelBlog.search函数内嵌回调函数获取数据库中的文章数组,然后再对回调结果进行处理,返回给回调函数reply,最后返回给前端使用。

通过一个简单的例子——“回调函数,内嵌回调函数”来说明回调函数是可以无穷尽的内嵌,结果就是各个部分之间高度耦合,流程混在一起,每个任务只能指定一个回调函数。最终造成的结果就会嵌入回调地狱,很可能就像这样了——结尾是无止境的});

图片来自于:
https://tutorialzine.com/media/2017/07/callback-hell.jpg

Promise 来源

所谓 Promise, 就是一个对象,用来传递异步操作的消息。它代表了某个未来才会知道结果的事件 (通常是一个异步操作),并且这个事件提供统一的 API,可供进一步处理。

————来自《ES 6标准入门 (第二版)》

基于回调函数的异步处理如果统一参数使用规则的话,写法也会很明了。但是,这也仅是编码规范而已,即使采用不同的写法也不会出错。

而 Promise 则是把类似的异步处理对象和处理规则进行规范化,并按照采用统一的接口来编写,而采用规定方法之外的写法都会出错。

除了 Promise 对象规定的方法 (这里的 then 或 catch )以外的方法都是不可以使用的,而不会像回调函数方式那样可以自己自由的定义回调函数的参数,而必须严格遵守固定、统一的编码方式来编写代码。

这样,基于 Promise 的统一接口的做法,就可以形成基于接口的各种各样的异步处理模式。所以,Promise 的功能是可以将复杂的异步处理轻松的进行模式化,这也可以说是使用 Promise 的理由之一。

Promise 在规范上规定 Promise 只能使用异步调用方式。
如果将上面的 demo 重新用 Promise 来写呢:

blogs.promise_search = (words) => {
    const promise = new Promise(function (resolve, reject) {
        const titleQuery = new AV.Query(Blog)
        titleQuery.contains("title", words);

        const descQuery = new AV.Query(Blog)
        descQuery.contains("desc", words);

        const tagsQuery = new AV.Query(Blog)
        tagsQuery.contains("tags", words);

        const wordsQuery = AV.Query.or(titleQuery, descQuery, tagsQuery);

        wordsQuery.descending("createdAt");
        wordsQuery.limit(5);
        wordsQuery.find().then(function (results) {
            resolve(results);
        }, function (error) {
            reject(error);
        });
    });

    return promise;
}

Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 和 reject。它们是两个函数,由 Javascript 引擎提供,不用自己传入。

其中,resolve 函数的作用是,将 Promise 对象的状态从“未完成”变成“成功” (即从 Pending 变为 Resolved),在异步操作成功时调用,并将异步操作的结果作为参数传递出去;

reject 函数的作用是,将 Promise 对象的状态从“未完成”变为“失败” (即从 Pending 变为 Rejected),在异步操作失败时调用,并将异步操作报出的错误作为参数传递出去。

Promise 实例执行以后,可以用then方法分别指定Resolved状态和Rejected状态的回调函数。所以在使用处:

const words = request.payload.words;
ModelBlog.promise_search(words).then(function (results) {
    let data = [];
    results.forEach(function(v) {
        let wrap = {};

        wrap.title = v.get("title");
        wrap.description = v.get("desc");
        wrap.picurl = v.get("picurl");
        wrap.url = v.get("url");

        data.push(wrap);
    });
    reply(data);
}).catch(function (error) {
    reply([]);
});

其中,then函数接受回调函数,作为 Promise 对象的状态变为 Resolved 时调用;而 catch 回调函数作为 Promise 对象的状态变为 Rejected 时调用。和【异步函数】相比,简单明了很多了,至少不用再传递回调函数到 ModelBlog 中,可以做到代码的分离,ModelBlog 的作用只是为了拿到数据,返回 Promise 对象,具体外界怎么使用,那是别人的事情了;同样在使用方,可以直接调用 Promise 对象,通过 then 方法处理回调数据和错误信息,代码也就更容易理解了。

但写代码总不能到处都是 Promise 对象,既然 Promise 能解决异步调用地狱的问题,但还有没有更好的办法将 Promise 异步方法写的和同步写法那样,毕竟很多人已经习惯面向过程的编写方式了?

Async/Await

Async/Await是一个很久就令人期待的 JavaScript 功能,它让使用异步函数更加愉快和容易理解。它是基于 Promise 的并且和现存的所有基于 Promise 的 API 相兼容。

从 async 和 await 这两个名字来的这两个关键字将会帮助我们整理我们的异步代码。

async function getBlogsAsync(words) {
    const titleQuery = new AV.Query(Blog)
    titleQuery.contains("title", words);

    const descQuery = new AV.Query(Blog)
    descQuery.contains("desc", words);

    const tagsQuery = new AV.Query(Blog)
    tagsQuery.contains("tags", words);

    const wordsQuery = AV.Query.or(titleQuery, descQuery, tagsQuery);

    wordsQuery.descending("createdAt");
    wordsQuery.limit(5);
    let results = await wordsQuery.find();
    return results;
}

这下连new Promise(...)都省了,直接写核心业务代码。很明显 Async/Await 版本的代码更短并且可读性更强。除了使用的语法,两个函数完全相同——他们都返回 Promise 并且都从数据库得到 Blogs 数据返回。在使用时,还是和之前一样,直接调用getBlogsAsync方法:

getBlogsAsync(words).then(function (results) {
    let data = [];
    results.forEach(function(v) {
        let wrap = {};

        wrap.title = v.get("title");
        wrap.description = v.get("desc");
        wrap.picurl = v.get("picurl");
        wrap.url = v.get("url");

        data.push(wrap);
    });
    reply(data);
}).catch(function (error) {
    reply([]);
});
总结

随着 Async/Await ,JavaScript语言在代码可读性和易用性上向前迈进了一大步。而且写异步代码,就跟常规的写面向过程的同步代码一样,简单直接明了。

最后分享几个相关资料,值得一看,还有更多深入的内容需要继续挖掘:

Javascript异步编程的4种方法。http://www.ruanyifeng.com/blog/2012/12/asynchronous%EF%BC%BFjavascript.html

《ES 6标准入门 (第二版)》,作者:阮一峰

JavaScript Async/Await Explained in 10 Minutes. https://tutorialzine.com/2017/07/javascript-async-await-explained

八段代码彻底掌握 Promise. https://juejin.im/post/597724c26fb9a06bb75260e8

JavaScript Promise迷你书(中文版) http://liubin.org/promises-book/#promises-overview

理解 async/await. https://juejin.im/post/596e142d5188254b532ce2da

听说最美的人和最帅的人,都会给作者打赏,以资鼓励

coding01 期待您关注

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

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

相关文章

  • 如何正确合理使用 JavaScript async/await

    摘要:想阅读更多优质文章请猛戳博客一年百来篇优质文章等着你引入的在的异步编程中是一个极好的改进。可能会产生误导一些文章将与进行了比较,并声称它是下一代异步编程风格,对此作者深表异议。结论引入的关键字无疑是对异步编程的改进。 showImg(https://segmentfault.com/img/bVbjFP0?w=800&h=450); 想阅读更多优质文章请猛戳GitHub博客,一年百来篇...

    trigkit4 评论0 收藏0
  • JavaScript是如何工作的:事件循环和异步编程的崛起+ 5种使用 async/await

    摘要:事件循环从回调队列中获取并将其推入调用堆栈。执行从调用堆栈中移除从调用堆栈中移除快速回顾值得注意的是,指定了事件循环应该如何工作,这意味着在技术上它属于引擎的职责范围,不再仅仅扮演宿主环境的角色。 此篇是 JavaScript是如何工作的第四篇,其它三篇可以看这里: JavaScript是如何工作的:引擎,运行时和调用堆栈的概述! JavaScript是如何工作的:深入V8引擎&编写...

    Honwhy 评论0 收藏0
  • JavaScript工作原理(四):事件循环,异步编程的兴起以及5招async/await实践

    摘要:事件循环从回调队列中获取并将其推送到调用堆栈。如何工作请注意,不会自动将您的回调函数放到事件循环队列中。它设置了一个计时器,当计时器到期时,环境将您的回调函数放入事件循环中,以便将来的某个事件会将其选中并执行它。 我们将通过回顾第一篇文章中单线程编程的缺点,然后在讨论如何克服它们来构建令人惊叹的JavaScript UI。在文章结尾处,我们将分享5个关于如何使用async / awai...

    piglei 评论0 收藏0
  • JavaScript 工作原理之四-事件循环及异步编程的出现和 5 种更好的 async/await

    摘要:函数会在之后的某个时刻触发事件定时器。事件循环中的这样一次遍历被称为一个。执行完毕并出栈。当定时器过期,宿主环境会把回调函数添加至事件循环队列中,然后,在未来的某个取出并执行该事件。 原文请查阅这里,略有改动。 本系列持续更新中,Github 地址请查阅这里。 这是 JavaScript 工作原理的第四章。 现在,我们将会通过回顾单线程环境下编程的弊端及如何克服这些困难以创建令人惊叹...

    maochunguang 评论0 收藏0
  • JavaScript 工作原理之四-事件循环及异步编程的出现和 5 种更好的 async/await

    摘要:函数会在之后的某个时刻触发事件定时器。事件循环中的这样一次遍历被称为一个。执行完毕并出栈。当定时器过期,宿主环境会把回调函数添加至事件循环队列中,然后,在未来的某个取出并执行该事件。 原文请查阅这里,略有改动。 本系列持续更新中,Github 地址请查阅这里。 这是 JavaScript 工作原理的第四章。 现在,我们将会通过回顾单线程环境下编程的弊端及如何克服这些困难以创建令人惊叹...

    浠ラ箍 评论0 收藏0
  • 重构:PromiseAsync/Await

    摘要:一方面,这里替代的是异步代码的编写方式,并非完全抛弃大家心爱的,地球人都知道是基于的,不用太伤心另一方面,是基于回调函数实现的,那也没有替代回调函数咯重构代码之后,我仍然用到了库。 摘要: 夸张点说,技术的发展与历史一样,顺之者昌,逆之者亡。JS开发者们,赶紧拥抱Async/Await吧! GitHub仓库: Fundebug/promise-asyncawait 早在半年多之前,...

    zhangfaliang 评论0 收藏0

发表评论

0条评论

阅读需要支付1元查看
<