JavaScript的异步编程是什么?

目录

前言:

同步任务和异步任务

使用回调

定时器

事件

 node的回调

期约(promise)

期约是什么?

什么是期约链呢?

接下来了解关键字async和await

await表达式

async函数

语法

错误处理

for/await循环

小结


前言:

JavaScript采用的是单线程模型,单线程模型指的是,JavaScript 只在一个线程上运行。也就是说,JavaScript 同时只能执行一个任务,其他任务都必须在后面排队等待。由于JavaScript采用单线程模型,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行。

但JavaScript的核心语言特性中没有一个是异步的,那么我们要怎么使用异步编程呢?

回调函数是异步操作最基本的方法。但JavaScript提供了三种重要的语言特性,可以让编写异步代码更容易。

ES6新增的期约(promise)是一种对象,代表了某个异步操作尚不可用的结果。还有关键字async和await它可以简化异步操作,它允许基于期约的异步代码写成同步的形式。最后还有异步迭代器for/await循环。允许在看起来同步的简单循环中操作异步事件流。

同步任务和异步任务

同步任务是那些没有被引擎挂起、在主线程上排队执行的任务。只有前一个任务执行完毕,才能执行后一个任务。

异步任务是那些被引擎放在一边,不进入主线程、而进入任务队列的任务。只有引擎认为某个异步任务可以执行了(比如 Ajax 操作从服务器得到了结果),该任务(采用回调函数的形式)才会进入主线程执行。排在异步任务后面的代码,不用等待异步任务结束会马上运行,也就是说,异步任务不具有“堵塞”效应。

使用回调

同步的回调函数:立即在主线程上执行,不会放入回调队列中,比如:数组遍历相关的回调、promise的executor函数。

异步的回调函数:不会立即执行,会放入回调队列以后再执行。比如:定时器、ajax、promise的成功与失败的回调。

回调:就是函数,可以传给其他函数,在其他函数满足某个条件,或发生某个异步事件的时候调用"回调"这个函数。(是我们没有调用,但是最终还是执行了的函数。)JavaScript的异步编程就是使用回调实现的。

定时器

定时器就是一个简单的异步操作。

使用setTimeout()函数来实现异步操作。setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式。

setTimeout(函数/表达式,毫秒)第一个参数是一个函数或者表达式,第二个参数是以毫秒为单位的时间间隔,表示在多少时间内调用前一个函数或表达式,setTimeout()只会调用一次指定的回调函数, 如果你只想重复执行可以使用 setInterval() 方法。使用 clearTimeout()可取消由 setTimeout() 方法设置的定时操作。

var myVar;
 
function myFunction() {
//3秒后执行alertFunc()函数
    myVar = setTimeout(alertFunc, 3000);
}
 
function alertFunc() {
    alert("Hello!");
}
// clearTimeout() 函数取消定时器 myVar
function myStopFunction() {
    clearTimeout(myVar);
}

事件

客户端的JavaScript编程几乎全是事件驱动的,比如鼠标按下事件,移动鼠标事件等。

事件驱动的JavaScript程序在特定的上下文中为特定类型的事件注册回调函数,而浏览器在指定的事件发生时调用这些函数。这些回调函数也称为事件处理程序或者事件监听器,通过document.addEventListener() 注册。 可以使用 document.removeEventListener() 方法来移除 addEventListener() 方法添加的事件

document.addEventListener(eventfunctionuseCapture)

参数描述
event必需。描述事件名称的字符串。
注意: 不要使用 "on" 前缀。例如,使用 "click" 来取代 "onclick"。
function必需。描述了事件触发后执行的函数。
当事件触发时,事件对象会作为第一个参数传入函数。 事件对象的类型取决于特定的事件。例如, "click" 事件属于 MouseEvent(鼠标事件) 对象。
useCapture可选。布尔值,指定事件是否 在捕获或冒泡阶段执行。
可能值:
  • true - 事件句柄在捕获阶段执行
  • false- 默认。事件句柄在冒泡阶段执行

 node的回调

node中也有回调和事件,比如读取文件内容默认的api也是异步的,会在读取内容后调用一个回调函数。具体可见node。要注意的是node使用的是on()方法而不是addEventListener()注册事件监听器。

期约(promise)

期约是什么?

期约是一个对象表示异步操作的结果。期约的结果可能就绪也可能未就绪,没有办法同步取得期约的值,只能要求期约在值就绪时调用一个回调函数。promise就是js中进行异步编程的一种新方案。

回调常遇到无法处理错误的问题,如果一个异步函数抛出异常,则该异常没有办法传播到异步操作的发起者。一个补救的方法时使用回调参数严密跟踪和传播错误并返回值,但是非常麻烦,容易出错,但是期约标准化了异步错误处理。通过期约链可以提供一个让错误正确传播的途径。

什么是期约链呢?

就是以线性then()方法调用链的形式表达一连串异步操作,而无需把每个操作嵌套在前一个操作的回调内部。期约链更容易表达一连串的异步操作。

由于期约具体内容较长,我将单独拿出来写一篇笔记。

具体可见:期约 promise——异步编程新方案_白芸哆的博客-CSDN博客

接下来了解关键字async和await

async和await是ES2017新增的关键字,这两个新关键字极大的简化了期约的使用,允许我们像编写因网络请求或其他异步事件而阻塞的同步代码一样,基于期约的异步代码。

await表达式

await表达式接收一个期约并将其转换成一个返回值或者一个抛出的异常,但我们通常不会使用await来接收一个保存期约的变量,而是把它放在一个会返回期约的函数调用前面。

let response = await fetch("/api/user/profile");
let profile = await response.json();

要注意的是任何使用await的代码本身就是异步的,并不会导致程序阻塞或者在指定期约落定前什么都不做。 因为任何使用await的代码都是异步的,所以它只能在异步函数 async 关键字声明的函数内部使用。

正常情况下,await 命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。

await针对所跟不同表达式的处理方式:

  • Promise 对象:await 会暂停执行,等待 Promise 对象 处理完毕,然后恢复 async 函数的执行并返回解析值。( Promise 对象 执行成功会返回promise成功的值,如果await的promise实例对象失败了,就会抛出异常,需要通过try{..可能出错的代码.}.catch(error){..}来捕获错误。)
  • 非 Promise 对象:直接返回对应的值。

async函数

语法

async function name([param[, param[, ... param]]]) { statements }
  • name: 函数名称。
  • param: 要传递给函数的参数的名称。
  • statements: 函数体语句。

把函数声明为 async意味着该函数的返回值将是一个期约,async函数返回一个 Promise 对象,可以使用 then方法添加回调函数。当函数执行的时候,一旦遇到 await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

async 函数有多种使用形式。

// 函数声明
async function foo() {}

// 函数表达式
const foo = async function () {};

// 对象的方法
let obj = { async foo() {} };
obj.foo().then(...)

// Class 的方法
class Storage {
  constructor() {
    this.cachePromise = caches.open('avatars');
  }

  async getAvatar(name) {
    const cache = await this.cachePromise;
    return cache.match(`/avatars/${name}.jpg`);
  }
}

const storage = new Storage();
storage.getAvatar('jake').then(…);

// 箭头函数
const foo = async () => {};

async函数内部return语句返回的值,会成为then方法回调函数的参数。

错误处理

如果await后面的异步操作出错,那么等同于async函数返回的 Promise 对象被 reject

防止出错的方法,也是将其放在try...catch代码块之中。

async function f() {
  try {
    await new Promise(function (resolve, reject) {
      throw new Error('出错了');
    });
  } catch(e) {
  }
  return await('hello world');
}

如果有多个await命令,可以统一放在try...catch结构中。

async function main() {
  try {
    const val1 = await firstStep();
    const val2 = await secondStep(val1);
    const val3 = await thirdStep(val1, val2);

    console.log('Final: ', val3);
  }
  catch (err) {
    console.error(err);
  }
}

要注意的是:await命令后面的Promise对象,运行结果可能是rejected(失败的),所以最好把await命令放在try...catch代码块中。 

async function myFunction() {
  try {
    await somethingThatReturnsAPromise();
  } catch (err) {
    console.log(err);
  }
}

// 另一种写法

async function myFunction() {
  await somethingThatReturnsAPromise()
  .catch(function (err) {
    console.log(err);
  });
}

多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。

let foo = await getFoo();
let bar = await getBar();

上面代码中,getFoogetBar是两个独立的异步操作(即互不依赖),被写成继发关系。这样比较耗时,因为只有getFoo完成以后,才会执行getBar,完全可以让它们同时触发。

// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;

上面两种写法,getFoogetBar都是同时触发,这样就会缩短程序的执行时间。

 为了理解async的工作原理,我们可以看一下后台发生了什么:

async function f(x){ / *函数体*/ }

把这个函数想象成一个返回期约的包装函数,它包装了你原始函数的函数体:

function f(x){
    return new Promise(function(resolve,reject){
        try{
            resolve((function(X){/*函数体*/})(x));
            }
            catch(e){
                    rejext(e);
            }
        });
}

 可以把await、关键字想象成分隔代码体的记号,它们把函数体分割成相对独立的同步代码块。

for/await循环

与常规的await表达式类似的是:for/await循环也是基于期约的。这里的异步迭代器会产生一个期约,而for/await循环等待该期约兑现,将兑现值赋给循环变量,任何在运行循环体,之后再从头开始,从迭代器取得另一个期约并等待这个新期约兑现。

小结

  1. 期约提供了一种结构化回调函数的新方式,是js中进行异步编程的新方案,期约还支持再then()调用链的末尾用catch()集中处理错误。
  2. async和await关键字可以让我们以同步代码的形式写出基于期约的异步代码。
  3. 异步迭代的对象可以再for/await循环中使用。

更多:

异步JavaScript MND:异步 JavaScript - 学习 Web 开发 | MDN

async 推荐学习:ES6 入门教程

Promise 推荐学习:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白芸哆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值