「ES6 深入」之 Promise/async await

890 阅读17分钟

余生很贵,努力活成自己想要的样子,愿你能穿运动鞋撸铁汗如雨下,也能穿高跟鞋潇洒貌美如花,不负青春,不负自己。


哈哈~ 认真起来的样子,很有魅力嘛 ☺️☺️☺️

Promise

基础语法及使用

Promise 定义

Promise 对象用于表示一个异步操作的最终完成 (或失败)及其结果值。ES6中新增一个内置的类:Promise 承诺/约定模式,基于这种模式可以有效的处理异步编程

Promise 描述

一个 Promise 对象代表在这个 promise 被创建出来时不一定已知的值。它让您能够把异步操作最终的成功返回值或者失败原因和相应的处理程序关联起来。 这样使得异步方法可以像同步方法那样返回值:异步方法并不会立即返回最终的值,而是会返回一个 promise实例

Promise 方法

Promise.all(iterable)

  • 语法Promise.all(iterable)
  • 参数:一个可迭代对象
  • 返回值:返回一个 Promise 实例
    • 此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve)Promise.all 返回的 promise 异步地变为完成。
    • 如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败的原因是第一个失败 promise 的结果。
    • 传入的可迭代对象为空,Promise.all同步地返回一个已完成(resolved)状态的promise。看下面例子

Promise.any()

  • 语法Promise.any(iterable);
  • 参数:一个可迭代的对象
  • 返回值:只要其中的一个 promise 成功,就返回那个已经成功的 promise

Promise.race()

  • 语法Promise.race(iterable);
  • 参数:一个可迭代的对象
  • 返回值:返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。(只要执行一个成功或者失败,返回的 promise 实例则是成功或者失败)

Promise.reject()

  • 语法Promise.reject(reason);
  • 参数:表示Promise被拒绝的原因
  • 返回值:返回一个失败原因为 reasonpromise 实例

Promise.resolve()

  • 语法Promise.reject(value);
  • 参数Promise对象解析的参数,也可以是一个Promise对象,或者是一个thenable
  • 返回值:返回一个带着给定值解析过的Promise对象,如果参数本身就是一个Promise对象,则直接返回这个Promise对象。

Promise.prototype.catch()

  • 语法
    • p.catch(onRejected);
    • p.catch(function(reason) {});
  • 参数.then() 方法注入的第二个函数 reason
  • 返回值catch() 方法返回一个Promise,并且处理拒绝的情况

Promise.prototype.finally()

  • 描述:返回一个Promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数
  • 语法
    • p.finally(onFinally);
    • p.finally(function() {// 返回状态为(resolved 或 rejected)});
  • 参数Promise 结束后调用的 Function
  • 返回值:返回一个设置了 finally 回调函数的 Promise 对象。

Promise.prototype.then() 「下翻看详细使用」

  • 描述:then() 方法返回一个 Promise。它最多需要有两个参数:Promise 的成功和失败情况的回调函数。
  • 语法
    • p.then(onFulfilled[, onRejected]);
    • p.then(value => {// fulfillment}, reason => {// rejection});
  • 参数
    • onFulfilled
    • onRejected
  • 返回值:返回一个设置了 finally 回调函数的 Promise 对象。

Promise 的三大概念

  1. executor 函数
  • new Promise 的时候会立即执行 executor 函数,在 executor 函数中管理了一个异步编程代码(同步代码也是OK的),此时Promise 状态是 pending;在我们异步操作成功或者失败的时候,通过执行 resolve 或者 reject 函数,可以有效的把实例状态变为成功状态或者失败状态,也就是从 pending 变为 fulfilled(成功)/rejected(失败);如果代码报错的情况下,也是把状态改为rejected(失败)
  1. 实例
  • 每一个 Promise 实例都具备[[PromiseState]][[PromiseResult]]方法,[[PromiseState]]promise 状态:pendingfulfilled/resolverejected三种状态;[[PromiseResult]]promise 值:默认是 undefined,一般存储成功的结果或者失败的原因。
  1. Promise 原型上的方法 .then(): 用来管控成功或者失败的操作
  • 实例可以调用 .then(),它会存放两个方法:resultreason (都是函数); 当p1 实例的状态修改为 fulfilled 的时候,通知传递的第一个函数(result)执行,result 就是[[PromiseResult]] 的值; 当p1 实例的状态修改为 rejected 的时候,通知传递的第二个函数(reason)执行,reason 就是[[PromiseResult]] 的值
  • 不论是否基于 THEN 注入了方法,执行 resolve/reject 的时候「修改状态和值」是同步的会立即处理,但是「通知对应注入方法执行」的这个任务是异步操作的,不会立即处理,只是把它排在等待任务队列中,当其他事情处理完,再次返回去,通知对应注入的方法执行
  • .then(onfulfilled, onrejected): 执行 then 方法只是把onfulfilled/onrejected函数保存起来了(这个保存的操作是同步的),但是此时函数还没有执行,当 promise 状态变为成功或者失败的时候,才会去触发执行对应的函数,then 是一个异步的微任务

Promsie 是如何管控异步编程的

  • new Promise 创建的实例,其状态和结果,取决于:
    • executor 函数中的 resolve/reject 执行
    • executor 函数执行是否报错
  • 执行 .then 方法返回一个全新的 promise 实例
  1. new Promise 的时候创建一个 promise 实例,此时在 executor 函数中管理一套异步的代码
  2. 后期等异步操作成功或者失败的时候,执行 resolve/reject,以此来控制 promise 实例的状态和结果
  3. 根据状态和结果,就可以控制基于.then 注入的两个方法中的哪一个去执行了

在看栗子

  1. 下述代码执行的顺序(executor 函数管控的是异步编程)
    • new Promise 构造函数执行
    • 执行 executor 函数:设置一个异步定时器
    • 执行实例.then(result, reason)注入两个方法,注入的方法会保存起来(此时这两个方法还没有执行)
    • 等待 1000ms
    • 执行定时器的回调函数:通过执行 resolve 改变 promise 状态和值
    • 通知之前基于.then()注入的两个方法中的某一个执行
let p1 = new Promise(function (resolve, reject) { 
  // new Promsie 的时候立即执行 executor 函数,在 executor 函数中管理了一个异步编程代码(此时状态是 pending);当异步操作到达指定时间,开始执行的时候(可以理解为异步操作成功),此时我们通过执行 resolve,把 promise 状态修改为 fulfilled;
  setTimeout(function(){
    resolve('OK');
  }, 1000)
});
console.log(p1); // Promise {<pending>}__proto__: Promise[[PromiseState]]: "pending"[[PromiseResult]]: undefined

p1.then(result=>{
  // 当p1 实例的状态修改为 fulfilled 的时候,通知传递的第一个函数执行,result 就是[[PromiseResult]] 的值
  console.log('成功', result)
}, reason=>{
  // 当p1 实例的状态修改为 rejected 的时候,通知传递的第二个函数执行,reason 就是[[PromiseResult]] 的值
  console.log('失败', reason)
});

下述代码管控的顺序(executor 函数管控的是同步代码)

  • new Promise 构造函数执行
  • 执行 executor 函数:
    • 输出 1
    • 立即修改状态和值,并且通知基于 THEN 注入的方法执行,此时 .THEN 方法还没有执行,方法还没有被注入,不知道该通知谁来执行,所以此时需要把通知方法执行的操作先保存起来,放入到等待任务队列中,这个操作本身是异步的,需要等待方法注入完成后再通知其执行
    • 输出 2(到此 executor 函数已经执行完成)
  • 执行实例.then(result, reason)注入两个方法,注入的方法会保存起来(此时这两个方法还没有执行)
  • 输入 3
  • 通知之前基于.then()注入的两个方法中的第一个执行(result方法)
let p1 = new Promise((resolve, reject)=>{
    console.log('1');// 1
    resolve('OK');
    console.log('2');
});

p1.then(result=>{
    console.log('成功:'+result);
}, reason=>{
    console.log('失败:'+reason);
});
console.log('3');

new Priomise 内部机制

  1. new Promise 的时候会立即执行传递的 executor 函数
  2. executor 函数中一般用来管控一个异步的操作(同步亦可)
  3. 而且传递给 executor 函数两个参数:resolve、reject,并且两个参数都是函数
  4. 创造 Promise 类的一个实例 p1,每一个 Promise 的实例都存在两个属性
    • [[PromiseState]] promise 状态:
      • pending: 准备状态
      • fulfilled(旧版本浏览器)/resolved(新版本浏览器): 成功状态(已兑现)
      • rejected: 失败状态(已拒绝)
      • 一旦状态从 pending 改变为 fulfilled 或者是 rejected, 都无法再次改变其状态
    • [[PromiseResult]] promise
      • 默认是 undefined,一般存储成功的结果或者失败的原因
      • 每个 Promise 实例指向 Promise 类的原型 p1.__proto__ = Promise.prototype
      • Promise 的原型上的方法:then/catch/finally
      • 如果 executor 函数中的代码执行报错,则实例的状态也会变为失败,并且 [[PromiseResult]]是报错的原因 看下面代码示例

new Promise 的时候必须要传入一个函数( executor 函数),否则报错

let p1 = new Promise();
console.log(p1);// 报错 Promise resolver undefined is not a function at new Promise

new Promise 的时候会立即执行传递的executor函数, 执行 resolve 控制实例的状态变为成功,传递的值 100,是成功的结果

[[PromiseState]]: 'fufilled' [[PromiseResult]]: 100

let p1 = new Promise(function (resolve, reject) { 
  resolve(100);
});
p1.then();

执行 reject 控制实例的状态变为失败,传递的值 NO,是失败的结果

[[PromiseState]]: 'rejected' [[PromiseResult]]: NO

let p1 = new Promise(function (resolve, reject) { 
  reject('NO');
});
p1.then();

THEN 方法

当前实例的状态已经是成功或者失败,此时创建一个异步的微任务,等待同步任务结束,根据成功还是失败,来决定执行哪个方法

  • 如果此时的状态还是 pending,则直接把方法存储起来即可,没有创建异步的微任务
  • 如果此时的状态已经改变(fulfilled/rejected),那么执行resolve/reject 的时候,会创建一个异步的微任务,等待同步任务结束后,根据状态去执行基于.then 动态存储的函数

看栗子

let p1 = new Promise(resolve => {
    // 1000ms 后,开始执行定时器,此时p1.then()中的两个方法已经注入完成
    setTimeout(function(){
        // 执行 resolve ,立即修改 promise 状态和值
        resolve('OK);
        console.log(1);
    }, 1000)
});

p1.then(result=>{
    console.log(2);
});
// 最终结果为 1 2
  • 输出 1 2
    • 说明 then 方法为异步:执行 resolve 后,不会等待执行 then 方法中的 result,而是放在一个事件队列中,先把同步代码执行完成输出 1,在去事件队列中找异步代码去执行输出 2

结论:不论是否基于 THEN 注入了方法,执行 resolve/reject 的时候「修改状态和值」是同步的会立即处理,但是「通知对应注入方法执行」的这个任务是异步操作的,不会立即处理,只是把它排在等待任务队列中,当其他事情处理完,再次返回去,通知对应注入的方法执行

.then() 分为两种情况执行

  1. .then(onfulfilled, onrejected) 返回的新Promise实例,其成功和失败取决于:onfulfilled/onrejected 执行是否报错以及返回结果(不管执行 onfulfilled 还是 onrejected, 只要执行不报错,则返回的新实例就是成功,[[PromiseResult]]就是返回给实例的结果)
let p1 = new Promise((resolve, reject)=>{
  setTimeout(()=>{
    <!-- resolve('OK'); -->
    <!-- reject('OK'); -->
  }, 1000);
});

let p2 = p1.then(result=>{
  console.log('成功', result);
  return 10;
}, reason=>{
  console.log('失败', reason);
  return 0;
})
console.log(p2);

执行 resolve('OK'); p2 是一个新的 promise 实例,它的 [[PromiseState]]fulfilled[[PromiseResult]] 为 10

执行 reject('NO'); p2 是一个新的 promise 实例,它的 [[PromiseState]]fulfilled[[PromiseResult]]0

  1. then方法返回结果是一个全新的 Promise 实例,则这个实例的成功和失败等内容决定了 p2 的成功和失败等内容
let p1 = new Promise((resolve, reject)=>{
  setTimeout(()=>{
    resolve('OK');
    <!-- reject('OK'); -->
  }, 1000);
});

let p2 = p1.then(result=>{
  console.log('成功', result);
  return new Promise((resolve, reject)=>{
    <!-- reject('新实例'); -->
    resolve('新实例');
  })
}, reason=>{
  console.log('失败', reason);
  return 0;
})

执行 .then 方法返回一个新的 promise 实例(promise1)return 的结果又是一个新的 promise 实例(promise2)的话,那么最终 p2 的结果等同于 returnpromise2 执行之后的结果

执行 reject('新实例'); console.log(p2); p2 是一个新的 promise 实例,它的 [[PromiseState]]rejected[[PromiseResult]] 为 '新实例'

执行 resolve('新实例'); console.log(p2); p2 是一个新的 promise 实例,它的 [[PromiseState]]fulfilled[[PromiseResult]] 为 '新实例'

Promise.resolve/reject: 直接返回指定状态的 promise 实例 Promise.all: 取决于多个 Promise 实例中是否出现有失败的

async await

async function 的定义async函数是使用async关键字声明的函数。

async函数是AsyncFunction构造函数的实例, 并且其中允许使用await关键字。

async和await关键字让我们可以用一种更简洁的方式写出基于Promise的异步行为,而无需刻意地链式调用promise

async函数还可以被作为表达式来定义。

async函数可能包含0个或者多个await表达式。

async awaitES7 新增的」 async:用来修饰函数,最后默认让函数返回一个 promise 实例(函数执行报错,实例状态是失败,结果是报错原因;否则实例状态是成功,结果是 return 后面的值), 返回一个 promise 实例的好处是把一个普通函数执行,执行后的结果默认让 promise 来管控,可以调用 .then 方法来处理逻辑,实现链式调用

async函数一定会返回一个promise对象。如果一个async函数的返回值看起来不是promise,那么它将会被隐式地包装在一个promise中。

async function fn() {
  return 10;
}
console.log(fn());

等价于

async function fn() {
  return Promise.resolve(10);
}

返回一个promise 实例,[[PromiseState]]: 'fulfilled', [[PromiseResult]]: '10'

async function fn() {
  if(true) throw new TypeError('11');
}
console.log(fn());

返回一个 promise 实例,[[PromiseState]]: 'rejected', [[PromiseResult]]: TypeError '11'

async function fn() {
  setTimeout(()=>{
    return 10;
  }, 1000)
}
console.log(fn());

执行fn()输出: 返回一个promise实例,[[PromiseState]]: 'fulfilled', [[PromiseResult]]: undefined

再看栗子

  • 从第一行代码直到(并包括)第一个await表达式(如果有的话)都是同步运行的
  • 如果函数体内有一个await表达式,async函数就一定会异步执行。
async function foo() {
   await 1
}

等价于

function foo() {
   return Promise.resolve(1).then(() => undefined)
}

结论:不能管控异步操作,只是用来修饰函数,使其返回一个 promise 实例,可以调用 .then 方法。 不管执行 resolve 还是 reject,只要代码不报错则为成功,[[PromiseState]] 则为 fulfilled[[PromsieResult]] return 的结果

await

await 操作符用于等待一个 Promise 对象。它只能在异步函数 async function 中使用。

await 的定义await 表达式会暂停当前 async function 的执行,等待 Promise 处理完成。若 Promise 正常处理(fulfilled),其回调的resolve函数参数作为 await 表达式的值,继续执行 async function

使用范围await关键字只在async函数内有效。如果你在async函数体之外使用它,就会抛出语法错误 SyntaxError

async/await的目的:为了简化使用基于promiseAPI时所需的语法。async/await的行为就好像搭配使用了生成器和promise

await 执行顺序await表达式会暂停整个async函数的执行进程并出让其控制权,只有当其等待的基于promise的异步操作被兑现或被拒绝之后才会恢复进程。promise的解决值会被当作该await表达式的返回值。使用async / await关键字就可以在异步代码中使用普通的try / catch代码块。

await返回值:一般 await 后面放的都是 promise 实例,如果设置的不是 promise 实例,返回该值本身;

await返回值分两种情况

  • 第一种情况:正常的值 await 10, 它会默认返回一个 promise 实例默认成功状态,结果是 return 后面的值
  • 第二种情况:函数执行 await fn()
    1. 先立即执行 fn() 函数,接收函数的返回值
    2. 判断 await 返回值是否是 promsie 实例
    3. 如果不是,则根据以上两种情况分析即可

来个 EG await 后的表达式是 promise 实例

  1. Promise 正常处理,状态为 (pending -> fulfilled), 回调的 resolve 函数参数作为 await 表达式的值,继续执行 async function
async function fn() {
  let result = await new Promise((resolve, reject)=>{
    setTimeout(()=>{
      resolve(10);
    }, 1000)
  });
  console.log(result);
}

console.log(fn());

执行 fn(); 输出:1000ms 后输出10

  1. Promise 处理异常( pending -> rejected ), await 表达式会把 Promise 的异常原因抛出
let p1 = new Promise((resolve, reject)=>{
  reject('2');
})

async function f1() { 
  let result = await p1;
  console.log(result);
}
f1(); 

输出

  Uncaught (in promise) 2

await 后的表达式不是 promise 实例

  1. 第一种情况:await 后的表达式是基本值
async function fn() {
  // 等同于 let result = await Promise.resolve(10);
  let result = await 10;
  console.log(result);
}
fn();

执行 fn(), 输出: 10 执行 console.log( fn() ); 输出一个 promise 实例「证实:async 会默认让函数返回一个 promise 实例」

  1. 第二种情况:await 后的表达式是函数
function func() { 
  return new Promise(resolve =>{
    resolve(2);
  })
};
async function fn() { 
  let result = await func();
  console.log(result);
};

fn();

输出

2

await 后的表达式是一个函数 func, 执行顺序为:

  • 立即执行 func() 函数, await 来接收 func() 的返回值
  • await 后的表达式是一个 promise 实例,并且 promise 状态为 fulfilled,则回调的 resolve 函数参数就是 await 表达式的值;也就是 let result = await 2
  • 继续执行 async function
  • 输出 result 值为 2

await 是一个异步的微任务,把当前上下文中await下面要执行的代码整体存储到异步的微任务中,当 await 后面的 promise 实例状态为成功后,再去执行下面的代码(也就是那个异步的微任务)

综合 EG

function func() { 
  console.log(1); 
  return new Promise(resolve =>{
    setTimeout(()=>{
      resolve(2);
    }, 1000)
  })
};

console.log(3); 

async function fn() { 
  console.log(4);
  let result = await func();

  console.log(result);
  console.log(5);
};

fn(); 
console.log(6);

执行顺序:

  • 浏览器自上而下执行,首先执行的是全局上下文中的同步代码,输出:3
  • 执行函数 fn(), async 函数体内,从第一行代码直到第一个 await 表达式都是同步运行的。输出:4
  • await 表达式之后的代码可以被认为是存在链式调用的then回调中,也就是把当前上下文中 await下面要执行的代码整体存储到异步的微任务中,当 await 后面的 promise 实例状态为成功后,再去执行下面的代码。此时,EventQueue 中微任务队列中存在一个任务。func() 函数体中的同步代码,输出:1
  • func() 返回的结果是一个异步的宏任务,放在EventQueue中宏任务队列中。
  • 浏览器不会去等待,会继续接着往下执行。输出:6
  • 同步代码执行完毕,浏览器会执行 Eventqueue 中的微任务队列, 查看是否有可执行的任务
  • 此时,[[PromiseState]]的状态为 pending, 所以微任务是一个不可执行的任务。
  • 那么接着找宏任务队列,过 1000ms 执行 resolve(2), 执行完毕,[[PromiseState]]状态从 pending 变为 fulfilled,宏任务执行完毕。
  • 浏览器继续到微任务队列中查找可执行的代码,promise 状态变为成功,await 后的代码可以执行,微任务队列中的代码执行,输出:2 5
  • EventQueue 中没有任务,则执行结束。最终输出:3 4 1 6 2 5

await表达式之后的代码可以被认为是存在在链式调用的then回调中,多个await表达式都将加入链式调用的then回调中,返回值将作为最后一个then回调的返回值。

注意promise链是分阶段构造的,因此在处理异步函数时必须注意对错误函数的处理。一般我们在 promise 链上配置了.catch处理程序,最后捕捉所有的错误

对失败的 promise 实例没有做异常处理,则控制台抛出异常,但是不会影响后续代码的执行

async function fn() {
  let result = await Promise.reject(1);
  console.log(result);
  setTimeout(()=>{
    console.log('定时器1')
  })
}
fn();
setTimeout(()=>{
  console.log('定时器2')
})

await 后的代码不执行, 但不影响后续代码的执行,输出

Uncaught (in promise) 1
定时器2

promise 实例处理代码抛异常的方法:promise.catch(reason=>{}) async await 处理代码抛异常的方法: try{...}catch(err){..}

如果直接使用以下方式处理异常:await 接收的是Promise.reject(1),如果调用.catch() 会返回一个新的 promise 实例,会与原意不符

let result = await Promise.reject(1).catch();

这种方式会报错

let i = 0;
function fn() { 
  console.log(++i);
  fn();
}

fn();// Maximum call stack size exceeded

不会报错,但是会形成死循环。在 EventLoop 机制中,只有主线程空闲才会执行异步的任务 每次调用 await ,都会形成一个异步的微任务,这里涉及两个队列到同步异步之间的切换,所以浏览器并不会报错

let i = 0;
async function fn() {
  console.log(++i);
  await Promise.resolve('OK');
  fn();
}
fn();