向JavaScript自定义中级开发者的目标前进(2) 〜用async/await取代Promise篇〜

aki发表于:2020年10月19日 09:58:16更新于:2022年02月25日 16:32:13

Index

概要

前篇《向JavaScript自定义中级开发者的目标前进(1) 〜webpack篇〜》介绍了Webpack的导入相关内容。
前篇里也说过了,导入Webpack之后可以使用最新的语法,其中之一就是async/await,本次具体介绍如何使用该语法更加简洁地进行异步处理。

async/await是指什么

async/await是指在访问API时,可实现和通常的代码写法一样,从上往下依次同步执行处理,并可等待诸如获取数据并将其放进变量里等处理结束后再继续后续处理的最新语法。(请参考下文的实际范例)

async/await使用了Promise对象,因此可以理解为是Promise处理的简洁版语法。

浏览器的支持情况

async/await不支持Internet Explorer 11(IE11)。但是,前篇也介绍了,使用Webpack进行编译的话,在IE11下也可以运行(因为带有Polyfill)。
如果不想用Webpack等,只想用此新语法,请注意在IE下无法运行。

Promise的问题点

Promise具有以下缺点。

  • 很难与使用Promise以外的常见语法写的代码进行比较
    如果不熟悉Promise,它可能比编写常规代码难的多。理由是通常语法的代码是自上而下依次处理的,但是Promise的话是使用resolve以及then等将处理串起来的。

  • 如果有连续多个异步处理,要用then()连起来,这更加复杂。

※在执行异步处理(例如与API进行通信)时,代码不会从上到下同步执行。 由于JavaScript的限制,无法等到返回响应。 (因为尝试这样做,浏览器会被冻结)

下面是使用Promise3次串联获取其他记录的处理的范例。

kintone.events.on(['app.record.create.submit', 'app.record.edit.submit'], function(event) {
  return new kintone.Promise(function(resolve, reject) {
    return kintone.api('/k/v1/record', 'GET', params1).then(function(resp1) { // 应答内容放在resp1里,等待处理结束后再继续后续处理
      console.log(resp1);
      return kintone.api('/k/v1/record', 'GET', params2);
    }).then(function(resp2) { // 应答内容放在resp2里,等待处理结束后再继续后续处理
      console.log(resp2);
      return kintone.api('/k/v1/record', 'GET', params3);
    }).then(function(resp3) { // 应答内容放在resp3里,等待处理结束后再继续后续处理
      console.log(resp3);
      resolve(event); // resolve返回promise处理完成
    });
  });
});

async/await的优点

使用async/await具有以下优点。

  • 在进行异步处理时,和使用Promise相比,更加简洁直观。

  • 连续进行多个异步处理时,也无需使用then()连接。

  • 由于Promise对象的处理方式与使用Promise时相同,因此也可以结合.then()以及.all()等Promise函数来使用。

和使用常见的Promise写法进行比较看看。

  • 使用Promise在打开记录详情页面时获取其他记录

    kintone.events.on('app.record.detail.show', () => {
      return kintone.api('/k/v1/record', 'GET', {app: 1, id: 1}).then((resp) => {
        console.log(resp); // 获取内容
      });
    }
  • 使用async/await在打开记录详情页面时获取其他记录

    kintone.events.on('app.record.detail.show', async () => {
      const resp = await kintone.api('/k/v1/record', 'GET', {app: 1, id: 1});
      console.log(resp); // 显示获取内容
    };

像这样写,就可以和常见的变量声明一样,将从API获取到的Response代入resp变量。

规则是:使用await时,要在其函数的前面加async,表示这是async函数。

async函数会返回Promise。因此,kintone端也可以等到结果回来之后再执行后续处理。

async/await的使用方法

像这样,在函数前面用async声明,需要等待的处理用await声明。但是请注意经常会出现忘记写async的情况。下面通过例子介绍实际在kintone上的使用方法。

async () => {
  await 异步处理
}

MDN上也有例子,可以结合那里一起学习。

async function

具体示例

我们再来看看其他具体例子。为了便于比较,准备了常见的Promise写法的例子和async/await的写法的例子。

例1 编辑记录时获取其他记录的数据作为正在编辑的记录的初始值

  • 使用Promise的例子

    kintone.events.on('app.record.edit.show', (event) => {
      const params = {app: 1, id: 1}; // 参数请指定任何内容
    
      // kintone.api()如果省略了第3参数,会返回Promise对象,直接return。
      // 用.then()连接,等待处理结束后继续后续处理
      return kintone.api('/k/v1/record', 'GET', params)
        .then((resp) => {
          // 用API获取的内容覆盖字段代码为单行文本框的字段值
          event.record.单行文本框.value = resp.record.单行文本框.value;
          return event;
        }).catch((e) => {
          // 在执行API之前发送诸如参数错误等错误时
          alert(e.message);
          return event;
        });
    });
  • 使用async/await的例子

    kintone.events.on('app.record.edit.show', async (event) => {
      const params = {app: 1, id: 1}; // 参数请指定任何内容
      try {
        // kintone.api()如果省略了第3参数,会返回Promise对象,直接return。
        // 将api的返回值代入变量
        const resp = await kintone.api('/k/v1/record', 'GET', params);
        // 用API获取的内容覆盖字段代码为单行文本框的字段值
        event.record.单行文本框.value = resp.record.单行文本框.value;
        return event;
      } catch(e) {
        // 在执行API之前发送诸如参数错误等错误时
        alert(e.message);
        return event;
      }
    });

※错误处理可以使用try/catch语法。

关于kintone.api返回的错误类型,可以参考以下文档。
https://cybozudev.kf5.com/hc/kb/article/200733/#step10

例2 在保存记录时获取3条记录并合计

  • 使用Promise的例子

     kintone.events.on(['app.record.create.submit', 'app.record.edit.submit'], (event) => {
      
      //kintone.api()如果省略了第3参数,会返回Promise对象,直接return。
      // 用.then()连接,等待处理结束后继续后续处理
      // ※ 本次作为示例特意写了串联,实际上这种情况可以使用Promise.all()。
      return kintone.api('/k/v1/record', 'GET', params1)
        .then((resp1) => {
          event.record.总计.value += Number(resp1.record.合计.value);
          // 第二次调用API
          return kintone.api('/k/v1/record', 'GET', params2);
          //用.then()连接,等待处理结束后继续后续处理
        }).then((resp2) => {
          event.record.总计.value += Number(resp2.record.合计.value);
          // 第3次调用API
          return kintone.api('/k/v1/record', 'GET', params3);
          // 用.then()连接,等待处理结束后继续后续处理
        }).then((resp3) => {
          event.record.总计.value += Number(resp3.record.合计.value);
          return event;
        }).catch((e) => {
          // 在执行API之前发送诸如参数错误等错误时
          alert(e.message);
          return event;
        });
    });
  • 使用async/await的例子

     kintone.events.on(['app.record.create.submit', 'app.record.edit.submit'], async (event) => { // 添加async
        try {
          //          ↓需要等待的处理前面加await
          const resp1 = await kintone.api('/k/v1/record', 'GET', params1); // 应答内容放在resp1里,等待处理结束后再继续后续处理
          const resp2 = await kintone.api('/k/v1/record', 'GET', params2); // 应答内容放在resp2里,等待处理结束后再继续后续处理
          const resp3 = await kintone.api('/k/v1/record', 'GET', params3); // 应答内容放在resp3里,等待处理结束后再继续后续处理
    
          // 到这里所有数据的获取都已经完成,可以进行计算了
          // 对resp1 - 3进行合计后赋值给“总计字段”
          event.record.总计.value = Number(resp1.record.合计.value) + Number(resp2.record.合计.value) + Number(resp3.record..value);
          return event;
        } catch(e) {
          // 在执行API之前发送诸如参数错误等错误时
          alert(e.message);
          return event;
        }
      });

特别是需要串联好几次的处理像上面那样写就易懂多了。

关于Promise的思考(Promise不是必须的)

并不是说只要使用了async/await就可以完全不用去了解Promise了,前面也说过它只是使异步处理的写法变得更加简单,但是还是需要理解Promise概念本身(如果能同时驾驭Promise最好)。

// async/await和Promise混在的写法的例子

const resp2 = await kintone.api('/k/v1/record', 'GET', {app: 1, id: 1}).then((resp1) => {
  return kintone.api('/k/v1/record', 'GET', {/* param因resp1而异 */});
});

// 如果是这样写,那像下面这样统一用async/await会比较好。
const resp1 = await kintone.api('/k/v1/record', 'GET', params1);
const resp2 = await kintone.api('/k/v1/record', 'GET', {/* param因resp1而异 */});

Promise和async/await的关系如下。

  • 使用await可以等待Promise的处理结束后再进行后续处理
    前面已经举过例子了,可以使用await等待kintone.api()的处理,那是因为kintone.api()返回了Promise对象。

  • async函数会返回Promise对象
    因为async函数会返回Promise对象,所以可以像上面那样,then和await混用。
    但是,这样容易因为混乱,所以最好不要像这样极端的写法。

最后

【kintone】2020 年1月12日定期维护联络

2020年1月定期维护之后,除了更改字段的事件之外,所有的事件都可支持Promise了。以前页面显示事件不支持Promise,因此无法用Promise来写异步处理。现在可以轻松实现了。可以使用Promise也就是说可以使用async/await,所以在进行获取其他应用的数据等异步处理时,请务必充分利用async/await。

不熟悉JavaScript以及异步处理的话,很难理解Promise这个概念。使用async/await的话,虽然实际上也是使用了Promise,但是代码看上去更加清晰直观。也许一开始不能理解,但经常使用会帮助你逐渐了解它。

此Tips在2020年4月版的kintone中确认过。