深入javascript计划六:深入浅出异步

什么是进程?

进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。

它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。

通俗来讲就是:一个进程就是一个程序的运行实例(详细解释就是,启动一个程序的时候,操作系统会为该程序创建一块内存,用来存放代码、运行中的数据和一个执行任务的主线程,我们把这样的一个运行环境叫进程)。

进程是系统资源分配的独立实体,每个进程都拥有独立的地址空间。

一个进程无法访问另一个进程的变量和数据结构。

如果想让一个进程访问另一个进程的资源,需要使用进程间通信(IPC)。

IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。其中 Socket和Streams支持不同主机上的两个进程IPC。

IPC通信介绍

什么是线程?

线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。

一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

通俗来讲就是:线程是进程的内部的一个执行序列

一个进程可以拥有多个线程,每个线程使用其所属进程的栈空间。

单线程,多线程概念

例子:

A = 1 + 2
B = 20 / 5
C = 7 * 8

拆分成4个任务

  • 任务 1 是计算 A=1+2
  • 任务 2 是计算 B=20/5
  • 任务 3 是计算 C=7*8
  • 任务 4 是显示最后计算的结果

单线程是如何执行任务的?

单线程会按照顺序分别执行这四个任务。

多线程是如何执行任务的?

多线程会分两步:

1.使用三个线程同时执行前面三个任务

2.等待第一步全部执行完毕再执行第四个显示任务

从图中可以看到,线程是依附于进程的,而进程中使用多线程并行处理能提升运算效率。

这时候就要讲讲线程安全了

简单测试你的线程是否安全:如果你的代码在多线程下执行和在单线程下执行永远都能获得一样的结果,那么你的多线程就是安全的。

如果不一致,那就可能是多线程的资源安全有问题,常见的情况如下:

1.临界资源问题(多个线程同时访问相同的资源并进行读写操作)

解决思路:互斥锁(防止多个线程同时读写某一块内存区域)

2.死锁(两个线程相互等待对方释放对象锁)

解决思路:

  • 加锁顺序:线程按照一定的顺序加锁
  • 加锁时限:线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁
  • 死锁检测

好了就介绍到这里,想了解更多

面试:史上最全多线程面试题 - (锁&内存模型&线程)

浅谈linux线程模型和线程切换

进程切换与线程切换的区别?

总结进程和线程

1.进程中的任意一线程执行出错,都会导致整个进程的崩溃。

A = 1+2
B = 20/0
C = 7*8

我把上述三个表达式稍作修改,在计算 B 的值的时候,我把表达式的分母改成 0,当线程执行到 B = 20/0 时,由于分母为 0,线程会执行出错,这样就会导致整个进程的崩溃,当然另外两个线程执行的结果也没有了。

2. 线程与线程之间共享进程中的数据。

如下图所示,线程之间可以对进程的公共数据进行读写操作。

从上图可以看出,线程 1、线程 2、线程 3 分别把执行的结果写入 A、B、C 中,然后线程 2 继续从 A、B、C 中读取数据,用来显示执行结果。

3. 当一个进程关闭之后,操作系统会回收进程所占用的内存。

当一个进程退出时,操作系统会回收该进程所申请的所有资源(即使其中任意线程因为操作不当导致内存泄漏,当进程退出时,这些内存也会被正确回收)。

比如之前的 IE 浏览器,支持很多插件,而这些插件很容易导致内存泄漏,这意味着只要浏览器开着,内存占用就有可能会越来越多,但是当关闭浏览器进程时,这些内存就都会被系统回收掉。

4. 进程与进程之间的内容相互隔离。

进程隔离是为保护操作系统中进程互不干扰的技术,每一个进程只能访问自己占有的数据,也就避免出现进程 A 写入数据到进程 B 的情况。正是因为进程之间的数据是严格隔离的,所以一个进程如果崩溃了,或者挂起了,是不会影响到其他进程的。

如果进程与进程之间需要进行数据的通信,这时候,就需要使用用于进程间通信(IPC)的机制了。

白话总结:

进程是运行中的程序,线程是进程的内部的一个执行序列

进程是资源分配的单元,线程是执行行单元

进程间切换代价大,线程间切换代价小

进程拥有资源多,线程拥有资源少

多个线程共享进程的资源

如果还不懂可以在看看   进程与线程的一个简单解释

什么是协程?

协程,英文名是 Coroutine, 又称为微线程,是一种比线程更加轻量级的存在。

正如一个进程可以拥有多个线程一样,一个线程也可以拥有多个协程。

协程不像线程和进程那样,需要进行系统内核上的上下文切换,协程的上下文切换是由程序所控制。

这样带来的好处就是性能得到了很大的提升,不会像线程切换那样消耗资源。

想了解更多?什么是协程

什么是并发?

并发是宏观概念,我分别有任务 A 和任务 B,在一段时间内通过任务间的切换完成了这两个任务,这种情况就可以称之为并发。

并发:是指两个或多个事件可以在同一个时间间隔发生

通俗来讲就是:并发就是A先走了1秒B马上跟上

什么是并行?

并行是微观概念,假设 CPU 中存在两个核心,那么我就可以同时完成任务 A、B。同时完成多个任务的情况就可以称之为并行。

并行:是指两个或多个事件可以在同一个时刻发生

通俗来讲就是:并行就是A、B两个人并排走路

异步与同步

异步:不等任务执行完,直接执行下一个任务。

同步:一定要等任务执行完了,得到结果,才执行下一个任务。

阻塞与非阻塞

所谓阻塞,指的是阻碍堵塞。它的本意可以理解为由于遇到了障碍而造成的动弹不得。

所谓非阻塞,自然是和阻塞相对,可以理解为由于没有遇到障碍而继续畅通无阻。

与同步、异步结合

所谓同步/异步,关注的是能不能同时开工。
所谓阻塞/非阻塞,关注的是能不能动。
通过推理进行组合:
同步阻塞,不能同时开工,也不能动。只有一条小道,一次只能过一辆车,可悲的是还TMD的堵上了。
同步非阻塞,不能同时开工,但可以动。只有一条小道,一次只能过一辆车,幸运的是可以正常通行。
异步阻塞,可以同时开工,但不可以动。有多条路,每条路都可以跑车,可气的是全都TMD的堵上了。
异步非阻塞,可以工时开工,也可以动。有多条路,每条路都可以跑车,很爽的是全都可以正常通行。

回到程序里,把它们和线程关联起来

同步阻塞,相当于一个线程在等待。
同步非阻塞,相当于一个线程在正常运行。
异步阻塞,相当于多个线程都在等待。
异步非阻塞,相当于多个线程都在正常运行。

想了解更多?

【面试】迄今为止把同步/异步/阻塞/非阻塞/BIO/NIO/AIO讲的这么清楚的好文章(快快珍藏)

上面说了这么多,就说一个白话版的

打群架

我(CPU)喊了3个人和我一起去打群架(这个 3个人的团伙  就是进程),进程好比是一个上下文,3个人之间 数据共享。

真正打的是谁?是里面的人( 也就是线程),所以线程是最基本的执行单元。

譬如3个人的团伙(这就是进程) 去打架。  对方也是3个人,那么就是1对1干了(这就是多线程操作)。

其实性能很低,完全可以先排一个人(线程)上去, 把对方打趴下后,立刻去打第二个人。 等第一个人站起来了再回过来打。
这种调度机制 ,就是协程(其实还是线程,只不过我加了技巧-调度机制)。

当一个线程(一个打手)打着打着扭在一起了, 那么这叫做阻塞。
我作为头领,可以立刻把这个打手,拉开。让他先去打弱一点的人(这叫做线程切换)。

线程和协程的区别在于:

协程是线程的一个打架技巧(调度策略),真正在干活的 永远是线程(系统根本不知道有协程的存在)。 

并发: 一个人上打A一拳,A还没缓过来,又打B一拳,B懵逼了,又打了C一拳。
看起来 仿佛在同一个时间点我打了三个人(这就是并发)。

并行: 3个人  同时出拳  打ABC ,ABC三个人同时受到一拳(这就是并行)。

同步:一个人打A,必须把A打死,才能打B 。

异步:一个人打A,A只要躺地上, 我就立刻去打B ,等A站起来,我再回头打A。

阻塞:我带3个小弟打架,我就是主线程,我打A ,发现打不过(此时就叫做阻塞)。

如果我硬着头皮打那么所有事就得等待,这叫做同步阻塞。

异步很简单,我打A打不过,我让我小弟去打A,我继续打B,小弟就是处理异步的另外一个线程,而我作为主线程是不能阻塞的,因为要打A打B打C这个过程是预先设定好的(代码里写了),小弟打A,如果打过了,我回头再来扇A一个巴掌  ,小弟打A没打过,我回头过来 向A认错(catch 了)。

好了回到javascript话题

回调函数(Callback)

回调函数应该是大家经常使用到的,以下代码就是一个回调函数的例子:

function ajax(url, succ, fail) {
    if (url === "/test") {
        if (typeof succ === "function") {
            return succ("succ")
        }
    }
    if (typeof fail === "function") {
        return fail("fail")
    }
}

ajax("/test", (res)=> {
    console.log(res) // succ
})

但是回调函数有一个致命的弱点,就是容易写出回调地狱(Callback hell)。假设多个请求存在依赖性,你可能就会写出如下代码:

function ajax(url, succ, fail) {
    if (url === "/test") {
        if (typeof succ === "function") {
            return succ("succ")
        }
    }
    if (typeof fail === "function") {
        return fail("fail")
    }
}

ajax("/test", (res)=> {
    console.log(res) // succ
    ajax("/test", (res)=> {
        console.log(res) // succ
        ajax("/test", (res)=> {
            console.log(res) // succ
        })
    })
})

以上代码看起来不利于阅读和维护,当然,你可能会想说解决这个问题还不简单,把函数分开来写不就得了。

function ajax(url, succ, fail) {
    if (url === "/test") {
        if (typeof succ === "function") {
            return succ("succ")
        }
    }
    if (typeof fail === "function") {
        return fail("fail")
    }
}

function firstAjax() {
    ajax("/test", (res)=> {
        console.log(res) // succ
        secondAjax()
    })
}
function secondAjax() {
    ajax("/test", (res)=> {
        console.log(res) // succ
    })
}
ajax("/test", (res)=> {
    console.log(res) // succ
    firstAjax()
})

以上的代码虽然看上去利于阅读了,但是还是没有解决根本问题。

回调地狱的根本问题就是:

  1. 嵌套函数存在耦合性,一旦有所改动,就会牵一发而动全身
  2. 嵌套函数一多,就很难处理错误

当然,回调函数还存在着别的几个缺点,比如不能使用 try catch 捕获错误,不能直接 return。

Promise

Promise 是 ES6 新增的语法,一定程度解决了回调地狱的问题。

Promise 翻译过来就是承诺的意思,这个承诺会在未来有一个确切的答复,并且该承诺有三种状态,分别是:

 

  1. 等待中(pending)
  2. 完成了 (resolved)
  3. 拒绝了(rejected)

这个承诺一旦从等待状态变成为其他状态就永远不能更改状态了,也就是说一旦状态变为 resolved 后,就不能再次改变

let promise = new Promise((resolve, reject) => {
    resolve('succ')
    // 无效
    reject('reject')
})
promise.then((res) => {
    console.log(res) // succ
})

  当我们在构造 Promise 的时候,构造函数内部的代码是立即执行的

let promise = new Promise((resolve, reject) => {
    console.log('new Promise')
    resolve('succ')
})
promise.then((res) => {
    console.log(res)
})
// new Promise
// succ

Promise 实现了链式调用,也就是说每次调用 then 之后返回的都是一个 Promise,并且是一个全新的 Promise,原因也是因为状态不可变。如果你在 then 中 使用了 return,那么 return 的值会被 Promise.resolve() 包装

Promise.resolve(1).then((res) => {
    console.log(res)
    return 2 // 包装成 Promise.resolve(2)
}).then(res => {
    console.log(res)
})
// 1
// 2

前面都是在讲述 Promise 的一些优点和特点,其实它也是存在一些缺点的,比如无法取消 Promise,错误需要通过回调函数捕获。

Promise 凭借什么消灭了回调地狱?

什么是回调地狱

  1. 多层嵌套的问题。
  2. 每种任务的处理结果存在两种可能性(成功或失败),那么需要在每种任务执行结束后分别处理这两种可能性。

这两种问题在回调函数时代尤为突出。Promise 的诞生就是为了解决这两个问题。

解决方法

Promise 利用了三大技术手段来解决回调地狱:

  • 回调函数延迟绑定
  • 返回值穿透
  • 错误冒泡

回调函数延迟绑定

function ajax(url) {
    return new Promise((resolve, reject) => {
        if (url === "/test") {
            resolve("succ")
        } else {
            reject("fail")
        }
    })
}
ajax("/test").then((res) => {
    console.log(res) // succ
})

看到没有,回调函数不是直接声明的,而是在通过后面的 then 方法传入的,即延迟传入。这就是回调函数延迟绑定。通俗来讲就是promise有resolved、rejected这两种延迟绑定。

返回值穿透

function ajax(url) {
    return new Promise((resolve, reject) => {
        if (url === "/test") {
            resolve("succ")
        } else {
            reject("fail")
        }
    })
}

let x = ajax("/test").then((res) => {
    console.log(res) // succ
    return ajax("/test")
})
x.then(res => {
    console.log(res) // succ
})

我们会根据 then 中回调函数的传入值创建不同类型的Promise,然后把返回的 Promise 穿透到外层,以供后续的调用。这里的 x 指的就是内部返回的 Promise,然后在 x 后面可以依次完成链式调用。

这便是返回值穿透的效果

这两种技术一起作用便可以将深层的嵌套回调写成下面的形式:

function ajax(url) {
    return new Promise((resolve, reject) => {
        if (url === "/test") {
            resolve("succ")
        } else {
            reject("fail")
        }
    })
}

ajax("/test").then((res) => {
    console.log(res) // succ
    return ajax("/test")
}).then(res => {
    console.log(res) // succ
    return ajax("/test")
}).then(res => {
    console.log(res) // succ
    return ajax("/test")
}).then(res => {
    console.log(res)
})

错误冒泡

function ajax(url) {
    return new Promise((resolve, reject) => {
        if (url === "/test") {
            resolve("succ")
        } else {
            reject("fail")
        }
    })
}

ajax("/test").then((res) => {
    console.log(res)
    return ajax("/test")
}).then(res => {
    console.log(res)
    return ajax("/test")
}).then(res => {
    console.log(res)
    return ajax("/test1")
}).then(res => {
    console.log(res)
}).catch(err => {
    console.log(err)
})

这样前面产生的错误会一直向后传递,被 catch 接收到,就不用频繁地检查错误了。

Promise 手写实现

// 三种状态
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";
// promise 接收一个函数参数,该函数会立即执行
function MyPromise(fn) {
  let _this = this;
  _this.currentState = PENDING;
  _this.value = undefined;
  // 用于保存 then 中的回调,只有当 promise
  // 状态为 pending 时才会缓存,并且每个实例至多缓存一个
  _this.resolvedCallbacks = [];
  _this.rejectedCallbacks = [];

  _this.resolve = function (value) {
    if (value instanceof MyPromise) {
      // 如果 value 是个 Promise,递归执行
      return value.then(_this.resolve, _this.reject)
    }
    setTimeout(() => { // 异步执行,保证执行顺序
      if (_this.currentState === PENDING) {
        _this.currentState = RESOLVED;
        _this.value = value;
        _this.resolvedCallbacks.forEach(cb => cb());
      }
    })
  };

  _this.reject = function (reason) {
    setTimeout(() => { // 异步执行,保证执行顺序
      if (_this.currentState === PENDING) {
        _this.currentState = REJECTED;
        _this.value = reason;
        _this.rejectedCallbacks.forEach(cb => cb());
      }
    })
  }
  // 用于解决以下问题
  // new Promise(() => throw Error('error))
  try {
    fn(_this.resolve, _this.reject);
  } catch (e) {
    _this.reject(e);
  }
}

MyPromise.prototype.then = function (onResolved, onRejected) {
  var self = this;
  // 规范 2.2.7,then 必须返回一个新的 promise
  var promise2;
  // 规范 2.2.onResolved 和 onRejected 都为可选参数
  // 如果类型不是函数需要忽略,同时也实现了透传
  // Promise.resolve(4).then().then((value) => console.log(value))
  onResolved = typeof onResolved === 'function' ? onResolved : v => v;
  onRejected = typeof onRejected === 'function' ? onRejected : r => throw r;

  if (self.currentState === RESOLVED) {
    return (promise2 = new MyPromise(function (resolve, reject) {
      // 规范 2.2.4,保证 onFulfilled,onRjected 异步执行
      // 所以用了 setTimeout 包裹下
      setTimeout(function () {
        try {
          var x = onResolved(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason);
        }
      });
    }));
  }

  if (self.currentState === REJECTED) {
    return (promise2 = new MyPromise(function (resolve, reject) {
      setTimeout(function () {
        // 异步执行onRejected
        try {
          var x = onRejected(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason);
        }
      });
    }));
  }

  if (self.currentState === PENDING) {
    return (promise2 = new MyPromise(function (resolve, reject) {
      self.resolvedCallbacks.push(function () {
        // 考虑到可能会有报错,所以使用 try/catch 包裹
        try {
          var x = onResolved(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (r) {
          reject(r);
        }
      });

      self.rejectedCallbacks.push(function () {
        try {
          var x = onRejected(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (r) {
          reject(r);
        }
      });
    }));
  }
};
// 规范 2.3
function resolutionProcedure(promise2, x, resolve, reject) {
  // 规范 2.3.1,x 不能和 promise2 相同,避免循环引用
  if (promise2 === x) {
    return reject(new TypeError("Error"));
  }
  // 规范 2.3.2
  // 如果 x 为 Promise,状态为 pending 需要继续等待否则执行
  if (x instanceof MyPromise) {
    if (x.currentState === PENDING) {
      x.then(function (value) {
        // 再次调用该函数是为了确认 x resolve 的
        // 参数是什么类型,如果是基本类型就再次 resolve
        // 把值传给下个 then
        resolutionProcedure(promise2, value, resolve, reject);
      }, reject);
    } else {
      x.then(resolve, reject);
    }
    return;
  }
  // 规范 2.3.3.3.3
  // reject 或者 resolve 其中一个执行过得话,忽略其他的
  let called = false;
  // 规范 2.3.3,判断 x 是否为对象或者函数
  if (x !== null && (typeof x === "object" || typeof x === "function")) {
    // 规范 2.3.3.2,如果不能取出 then,就 reject
    try {
      // 规范 2.3.3.1
      let then = x.then;
      // 如果 then 是函数,调用 x.then
      if (typeof then === "function") {
        // 规范 2.3.3.3
        then.call(
          x,
          y => {
            if (called) return;
            called = true;
            // 规范 2.3.3.3.1
            resolutionProcedure(promise2, y, resolve, reject);
          },
          e => {
            if (called) return;
            called = true;
            reject(e);
          }
        );
      } else {
        // 规范 2.3.3.4
        resolve(x);
      }
    } catch (e) {
      if (called) return;
      called = true;
      reject(e);
    }
  } else {
    // 规范 2.3.4,x 为基本类型
    resolve(x);
  }
}

Generator

生成器(Generator)是 ES6 中的新语法,最大的特点就是可以控制函数的执行,相对于之前的异步语法,上手的难度还是比较大的。因此这里我们先来好好熟悉一下 Generator 语法。

  1. 使用 * 表示这是一个 Generator 函数
  2. 内部可以通过 yield 暂停代码
  3. 通过调用 next 恢复执行

简单的例子:

function* foo() {
    yield 'result1'
    yield 'result2'
    yield 'result3'
}

const gen = foo()
console.log(gen.next())
console.log(gen.next())
console.log(gen.next())
console.log(gen.next())
// { value: 'result1', done: false }
// { value: 'result2', done: false }
// { value: 'result3', done: false }
// { value: undefined, done: true }

很好理解每次gen.next(),程序继续执行,直到遇到 yield 程序暂停。

next 方法返回一个对象, 有两个属性: value 和 done。

value 为当前 yield 后面的结果,done 表示是否执行完。

复杂一点的例子:

function *foo(x) {
    let y = 2 * (yield (x + 1))
    let z = yield (y / 3)
    return (x + y + z)
}
let it = foo(5)
console.log(it.next())   // => {value: 6, done: false}
console.log(it.next(12)) // => {value: 8, done: false}
console.log(it.next(13)) // => {value: 42, done: true}

讲解:

1.当执行第一次 next 时,传参会被忽略,并且函数暂停在 yield (x + 1) 处,所以返回 5 + 1 = 6。

2.当执行第二次 next 时,传入的参数等于上一个 yield 的返回值,如果你不传参,yield 永远返回 undefined

此时 let y = 2 * 12,所以第二个 yield 等于 2 * 12 / 3 = 8。

3.当执行第三次 next 时,传入的参数会传递给 z,所以 z = 13, x = 5, y = 24,相加等于 42。

如果生成器要调用另一个生成器时如何操作?

答案:yield*

看例子:

function* gen1() {
    yield 1;
    yield 4;
}
function* gen2() {
    yield 2;
    yield 3;
}

如果我们想按照1234顺序执行,如何来做?

看例子:

function* gen1() {
    yield 1;
    yield* gen2();
    yield 4;
}
function* gen2() {
    yield 2;
    yield 3;
}

let g1 = gen1()
console.log(g1.next())
console.log(g1.next())
console.log(g1.next())
console.log(g1.next())
console.log(g1.next())

// { value: 1, done: false }
// { value: 2, done: false }
// { value: 3, done: false }
// { value: 4, done: false }
// { value: undefined, done: true }

 生成器实现机制——协程

可能你会比较好奇,生成器究竟是如何让函数暂停, 又会如何恢复的呢?这时候就要讲协程了(上文有介绍)。

javascript不是单线程执行嘛?难道可以开多协程一起执行嘛?

答案是:并不能。一个线程一次只能执行一个协程。

比如当前执行 A 协程,另外还有一个 B 协程,如果想要执行 B 的任务,就必须在 A 协程中将JS 线程的控制权转交给 B协程,那么现在 B 执行,A 就相当于处于暂停的状态。

例子:

function* A() {
  console.log("我是A");
  yield B(); // A停住,在这里转交线程执行权给B
  console.log("结束了");
}
function B() {
  console.log("我是B");
  return 100;// 返回,并且将线程执行权还给A
}
let gen = A();
gen.next();
gen.next();

// 我是A
// 我是B
// 结束了

讲解:

在这个过程中,A 将执行权交给 B,也就是 A 启动 B,我们也称 A 是 B 的父协程。因此 B 当中最后return 100其实是将 100 传给了父协程。

Generator 手写实现

// cb 也就是编译过的 test 函数
function generator(cb) {
  return (function() {
    var object = {
      next: 0,
      stop: function() {}
    };

    return {
      next: function() {
        var ret = cb(object);
        if (ret === undefined) return { value: undefined, done: true };
        return {
          value: ret,
          done: false
        };
      }
    };
  })();
}
// 如果你使用 babel 编译后可以发现 test 函数变成了这样
function test() {
  var a;
  return generator(function(_context) {
    while (1) {
      switch ((_context.prev = _context.next)) {
        // 可以发现通过 yield 将代码分割成几块
        // 每次执行 next 函数就执行一块代码
        // 并且表明下次需要执行哪块代码
        case 0:
          a = 1 + 2;
          _context.next = 4;
          return 2;
        case 4:
          _context.next = 6;
          return 3;
		// 执行完毕
        case 6:
        case "end":
          return _context.stop();
      }
    }
  });
}

async/await

async/await被称为 JS 中异步终极解决方案。

什么是async?

async 它就是 Generator 函数的语法糖

async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。

async函数对 Generator 函数的改进,体现在以下四点。

  1. 内置执行器
  2. 更好的语义
  3. 更广的适用性
  4. 返回值是Promise

例子:

async function test() {
  return "1";
}
console.log(test());

async function test() {
  return "1";
}
console.log(test());
test().then(res => {
    console.log(res) // 1
})

await

await 只能在 async 函数中使用(正常情况下,await命令后面是一个 Promise 对象,返回该对象的结果(resolve或者reject的值)。如果不是 Promise 对象,就直接返回对应的值),await 表达式会暂停当前 async function 的执行。

例子:

async function f() {
  // 等同于
  // return 123;
  return await 123;
}

f().then(v => console.log(v))
// 123

上面代码中,await命令的参数是数值123,这时等同于return 123

例子:

async function test() {
    console.log("执行了test方法")
    let a = await 200
    // let promise = new Promise((resolve, reject) => {
    // resolve(200)
    // })
    // let a = promise // 会监听promise变化
    // let a = promise.then((res) => {})
    console.log("执行test方法await-", a)
    console.log("----执行完test方法-----")
}
console.log('打印0')
test()
console.log('打印300')
console.log('打印400')
console.log('打印500')

let p = new Promise((resolve, reject) => {
    console.log("执行了p的promise")
    resolve(800)
})
// 监听p的promise状态变化
p.then(res => {
    console.log("监听到p的promise变化-", res)
})
// 打印0
// 执行了test方法
// 打印300
// 打印400
// 打印500
// 执行了p的promise
// 执行test方法await- 200
// ----执行完test方法-----
// 监听到p的promise变化- 800

讲解:

首先代码按顺序执行,先执行(console.log('打印0')),然后将test()压入执行栈,执行(console.log("执行了test方法")),下面遇到关键字await:

1.await内部实现了generators,generators会保留堆栈中东西

2.await是异步操作,遇到await就会立即返回一个pending状态的Promise对象,暂时返回执行代码的控制权

await 200

被js引擎转换成一个promise: 

let promise = new Promise((resolve,reject) => {
   resolve(200);
})
let a = promise // 会监听promise变化

如2说的,所以这时候函数外的代码得以继续执行,所以会按序执行(console.log('打印300')),console.log('打印400'),console.log('打印500'),执行到p的promise内部执行(console.log("执行了p的promise")),遇到resolve,resolve任务进入微任务队列,当前线程的宏任务完成,现在检查微任务队列,先执行test方法里面的(console.log("执行test方法await-", a))、console.log("----执行完test方法-----"),然后执行console.log("监听到p的promise变化-", res)。

在看例子:

async function test() {
    console.log("执行了test方法")
    let a = await 200
    console.log("执行test方法await-", a)
    let b = await 202
    console.log("执行test方法await-", b)
    console.log("----执行完test方法-----")
}
console.log('打印0')
test()
console.log('打印300')
console.log('打印400')
console.log('打印500')

let p = new Promise((resolve, reject) => {
    console.log("执行了p的promise")
    resolve(800)
})
p.then(res => {
    console.log("监听到p的promise变化-", res)
})
// 打印0
// 执行了test方法
// 打印300
// 打印400
// 打印500
// 执行了p的promise
// 执行test方法await- 200
// 监听到p的promise变化- 800
// 执行test方法await- 202
// ----执行完test方法-----

在看例子:

async function test() {
    console.log("执行了test方法")
    let promise = new Promise((resolve, reject) => {
        console.log("执行了test里面的Promise内部")
        resolve(222)
    })
    let a = await promise
    console.log("执行test方法await-", a)
    console.log("----执行完test方法-----")
}
console.log('打印0')
test()
console.log('打印300')
console.log('打印400')
console.log('打印500')

let p = new Promise((resolve, reject) => {
    console.log("执行了p的promise")
    resolve(800)
})
p.then(res => {
    console.log("监听到p的promise变化-", res)
})

// 打印0
// 执行了test方法
// 执行了test里面的Promise内部
// 打印300
// 打印400
// 打印500
// 执行了p的promise
// 执行test方法await- 222
// ----执行完test方法-----
// 监听到p的promise变化- 800

想了解更多?

https://yuchengkai.cn/docs/frontend/#async-%E5%92%8C-await

https://es6.ruanyifeng.com/#docs/async

好了让我们实现一个sleep

function sleep(n) {
    return new Promise(resolve => {
        setTimeout(() => {resolve(n)}, n);
    });
}
async function test() {
    console.log("------开始----")
    await sleep(1800);
    console.log("----结束----");
}
test()

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

An_s

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

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

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

打赏作者

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

抵扣说明:

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

余额充值