promise 、async/await 的原理及实现

前言

事件循环机制

由于 javascript 引擎是采用单线程运行机制,执行耗时过大的操作时会造成页面的阻塞,为了解决页面的阻塞问题,js 将任务分为 同步任务、异步任务,随之而来的是异步带来的执行顺序问题。

而 promise 的出现能为我们解决什么样的问题呢?
在传统的异步实现中,我们可以使用回调函数来实现异步编程,通过层层嵌套回调来满足这种异步的依赖关系,如果嵌套层数过多,可读性和可维护性都变得很差,产生所谓“回调地狱”,而Promise将回调嵌套改为链式调用,增加可读性和可维护性。

什么是回调函数?

把一个函数当作参数传递,传递的是函数的定义并不会立即执行,而是在将来特定的时机再去调用,这个函数就叫做回调函数。
在定时器setTimeout以及Ajax的请求时都会用到回调函数。

setTimeout(function(){ //这个function()就是回调函数,它只有在1秒后才会执行 
    console.log('执行了回调函数'); 
},1000)

在日常的工作中我们常常会碰到一下情况:

  • 接口的调用。
  • 请求网络资源(例如,文本文件、图像文件、二进制文件等)。

为了处理这些情况必须使用异步的操作,而回调是处理这些情况的一种方式,所以从本质上说回调函数是异步的。

回调地狱又是什么?

回调函数的层层嵌套被称之为 回调地狱

存在异步任务的情况下,为了确保异步任务能够按照顺序执行,我们将回调层层嵌套,此时就出现了回调地狱。回调地狱的出现会导致:

  • 代码臃肿,可读性差
  • 复用性差、扩展性差
  • 高耦合、可维护性差
  • 只能在回调中处理异步问题
setTimeout(function () {  //第一层
            console.log(111);
            setTimeout(function () {  //第二层
                console.log(222);
                setTimeout(function () {   //第三层
                    console.log(333);
                }, 1000)
            }, 2000)
        }, 3000)

如何避免回调地狱?

  1. promise
  2. async/await
  3. 使用 async.js 库: Async 是一个工具模块,它提供了直接、强大的函数来使用异步 JavaScript

promise

什么是promise?

Promise 意为 ‘承诺’,意思是在未来的某个时间点承诺返回数据给你。它是js中的一个原生对象,是一种异步编程的解决方案,可以替换掉传统的回调函数解决方案。

  1. Promise 是一个类,在执行这个类的时候会传入一个执行器,这个执行器会立即执行
  2. promise对象有三个状态:pending(进行中),resolved(已成功),rejected(已失败)

** 如何改变promise的状态**:promise 通过 执行器的 resolvereject 两个函数来更改状态,

  • resolve(value): promise的状态由 pending ===>> resolved
  • reject(error): promise的状态由 pending ===>> rejected
  • 抛出异常: promise的状态由 pending ===>> rejected
  • 一旦 promise 的状态发生变化后将不会再被改变。
  1. then 对 promise 中返回结果进行处理,then会返回一个 新 的 Promise 实例,因此可实现链式调用。

promise本身只是一个容器, 真正异步的是它的两个回调 resolve()reject()。
promise本质 不是控制 异步代码的执行顺序(无法控制), 而是控制异步代码结果处理的顺序

创建一个 promise

1.new Promise(executor)

promise 状态只能在 promise 内部进行操作,通过 new Promise 创建 promise 对象时,Promise 必须接受一个回调函数作为参数,我们称该函数为执行器函数,promise 的内部操作在执行器函数中执行。
回调函数 executor:(resolve, reject) => {},也叫 “执行器”,执行器 executor 是同步执行的,只有 then() 里面的回调处理才是异步的,因为它需要等待主体任务执行结束
执行器函数又包含resolvereject两个参数。

  • resolve : 将Promise对象的状态从 Pending(进行中) 变为 resolved(已成功)
  • reject : 将Promise对象的状态从 Pending(进行中) 变为 rejected(已失败),并抛出错误
let promise = new Promise((resolve,reject)=>{
    // 接收一个callback。参数是成功函数与失败函数
	setTimeout(()=>{
       let num = parseInt(Math.random()*100);
       // 如果数字大于50就调用成功的函数,并且将状态变成Resolved
       if(num > 50){
          resolve(num);
       }else{
        // 否则就调用失败的函数,将状态变成Rejected
          reject(num)
       }
	},10000)
})

2.Promise.resolve

除了使用 new 实例化 promise 外还可以使用 Promise.resolve(value) 返回一个 promise 对象,可以对返回值进行.then调用

  • Promise.resolve(value) 方法返回一个以给定值解析后的Promise 对象。
  • 如果这个值是一个 promise ,那么将返回这个 promise ;
  • 如果这个值是thenable(即带有"then"方法),返回的promise会“跟随”这个thenable的对象,采用它的最终状态;否则返回的promise将以此值完成。
Promise.resolve(11).then((value)=>{console.log(value)})

Promise.resolve() 返回任意一个非 promise 的值都会被包裹成 promise 对象

Promise.resolve().then(() => {
  return new Error('error!!!')
}).then(res => {
  console.log("then: ", res)
}).catch(err => {
  console.log("catch: ", err)
})

//  ----------------answer------------------
"then: " "Error: error!!!"

Promise.resolve() 返回任意一个非 promise 的值都会被包裹成 promise 对象,return new Error(‘error!!!’) 也被包裹成了return Promise.resolve(new Error(‘error!!!’)) 因此不会被catch捕获。

3.Promise.reject

Promise.reject() 方法返回一个带有拒绝原因的Promise对象。

Promise.reject(value) 也是实例化 promise 的一种快捷方式,但是最终返回的 promise 的状态为 rejected

Promise.reject(new Error('error!!!'));

Promise 的实例方法

then

promise.then(resolved, rejected)

then 接收两个函数

  • 第一个是 promise 的状态转变为 resolved 时调用的回调函数 resolved()
  • 第二个是 promise 转变为 rejected 是调用的回调函数 rejected()

then 会返回一个新的 promise 对象,将 then 的返回值包装成 Promise,所以 then 方法支持 链式调用
只有 promise 在执行了 resolve 之后,才会触发 then 回调函数的执行

promise.resolve(1).then(res=>{
    console.log(res);
    //在构造函数中如果你执行力resolve函数就会到这一步
},err=>{
    // 执行了reject函数会到这一步
    console.log(err);
})

catch

该方法相当于 then 方法的第二个参数,指向 reject 的回调函数。会返回一个新的 promise

不过 catch 方法还有一个作用,就是在执行 resolve 回调函数时,如果出现错误,抛出异常,不会停止运行,而是进入 catch 方法中。
catch 不管在哪里都能捕获到上层未捕获的错误
能被 catch 捕获的两种情况

  • throw new Error
  • Promise.reject()

例如:

Promise.resolve().then(() => {
  return new Error('error!!!')
}).then(res => {
  console.log("then: ", res)
}).catch(err => {
  console.log("catch: ", err)
})

//  ----------------answer------------------
"then: " "Error: error!!!"

.then 或者 .catch 中 return 一个 error 对象并不会抛出错误,Promise.resolve() 将抛出的对象包装成Promise.resolve(new Error(‘error!!!’)), 所以不会被后续的 .catch 捕获。
修改以上代码,使得 ‘error!!!’ 抛出时能被catch 捕获可以使用以下两种方法:

return Promise.reject(new Error('error!!!'));
// or
throw new Error('error!!!')

听说在捕获到异常后还能够继续then,那就来试试吧

let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(10)  
    }, 1000)
}).then(() => {
       throw Error("1123")
}).catch((err) => {
    console.log(err);
})
.then(() => {
    console.log('异常捕获后可以继续.then');
})

果然如此 当第一个 .then 的异常被捕获后 catch 会返回一个新的 promise 所以 后续的then 仍能够继续执行。

Error: 1123
"异常捕获后可以继续.then"

finally

finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作

  1. .finally()方法不管Promise对象最后的状态如何都会执行
  2. finally 的回调函数不接受任何参数,因此无法判断前面的 Promise 状态到底是resolved还是 rejected。finally方法里面的操作,不依赖于 Promise 的执行结果,与状态无关的。
  3. 它最终返回的默认会是一个上一次的Promise对象值,不过如果抛出的是一个异常则返回异常的Promise对象。
Promise.resolve('1')
  .then(res => {
    console.log(res)
  })
  .finally(() => {
    console.log('finally')
  })
Promise.resolve('2')
  .finally(() => {
    console.log('finally2')
  	return '我是finally2返回的值'
  })
  .then(res => {
    console.log('finally2后面的then函数', res)
  })



//  ----------------answer------------------
'1'
'finally2'
'finally'
'finally2后面的then函数' '2'

若是 finally 中抛出的是一个异常会怎样处理

function promise1 () {
  let p = new Promise((resolve) => {
    console.log('promise1');
    resolve('1')
  })
  return p;
}
function promise2 () {
  return new Promise((resolve, reject) => {
    reject('error')
  })
}
promise1()
  .then(res => console.log(res))
  .catch(err => console.log(err))
  .finally(() => console.log('finally1'))

promise2()
  .then(res => console.log(res))
  .catch(err => console.log(err))
  .finally(() => console.log('finally2'))

//  ----------------answer------------------

promise1'
'1'
'error'
'finally1'
'finally2'

值透传

值穿透指的是,链式调用的参数不是函数时,会发生值穿透,就传入的非函数值忽略,传入的是之前的函数参数。

.then 或者 .catch 的参数期望是函数,传入非函数则会发生值透传。

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)

//  ----------------answer------------------
1

只有传入的是函数时才会传递给下一个链式调用

Promise.resolve(1)
    .then(function () {
        return 2
    })
    .then(() => { Promise.resolve(3) })
    .then(console.log)

//  ----------------answer------------------
undefined

此时的箭头函数没有返回值,所以 console 的结果为 undefined。

小结

  1. .then方法是能接收两个参数的,第一个是处理成功的函数,第二个是处理失败的函数,也可以认为catch是then的第二个参数的简便写法。
  2. .then 和 .catch 都会返回一个新的 Promise
  3. .then 或 .catch 返回的值不能是 promise 本身,否则会造成死循环
  4. .then 或者 .catch 的参数期望是函数,传入非函数则会发生值透传。.

Promise 的静态方法

Promise.all()

Promise.all 并发的执行一组异步任务,并且在所有异步操作执行完后才执行回调。

Promise.all() 接收一个数组,数组的每一项都是一个promise对象,会返回一个 Promise 实例。
当数组中所有的 promise 的状态都达到 resolved 的时候,all 方法的状态就会变成 resolved
如果有一个状态变成了 rejected,那么 all 方法的状态就会变成 rejected,失败原因的是第一个失败 promise 的结果。

function runAsync (x) {
    const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
    return p
}
Promise.all([runAsync(1), runAsync(2), runAsync(3)])
  .then(res => console.log(res))


//  -------------answer---------------------------

1
2
3
[1, 2, 3]

.all() 后面的 .then() 里的回调函数接收的就是所有异步操作的结果。
结果中数组的顺序与 Promise.all() 接收到的数组顺序一致

function runAsync (x) {
  const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
  return p
}
function runReject (x) {
  const p = new Promise((res, rej) => setTimeout(() => rej(`Error: ${x}`, console.log(x)), 1000 * x))
  return p
}
Promise.all([runAsync(1), runReject(4), runAsync(3), runReject(2)])
  .then(res => console.log(res))
  .catch(err => console.log(err))


// ----------------answer-------------
// 1s后输出
1
3
// 2s后输出
2
Error: 2
// 4s后输出
4

catch 是会捕获最先的那个异常,在这道题目中最先的异常就是 runReject(2) 的结果,
Error: 4 将不会被 catch 捕获,而是会进入 then 的第二个参数。
另外,如果一组异步操作中有一个异常都不会进入 then() 的第一个回调函数参数中。

Promise.all([runAsync(1), runReject(4), runAsync(3), runReject(2)])
  .then(res => console.log(res), 
  err => console.log(err))

Promise.race()

Promise.race() 将一组异步任务中最先完成或失败的 Promise 返回成一个新的Promise。

如果第一个 promise 对象状态变成 resolved,那自身的状态变成了resolved;
反之第一个 promise 变成rejected,那自身状态就会变成 rejected

const p1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 500, 1)
})

const p2 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 2)
})

Promise.race([p1, p2]).then((value) => {
  console.log(value) // 2
})

Promise.race([p1, p2, 3]).then((value) => {
  console.log(value) // 3
})


最先执行完成的,就执行相应后面的.then或者.catch。谁先以谁作为回调

function runAsync (x) {
  const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
  return p
}
Promise.race([runAsync(1), runAsync(2), runAsync(3)])
  .then(res => console.log('result: ', res))
  .catch(err => console.log(err))


// ----------------answer-------------
1
'result: ' 1
2
3

它只会获取最先执行完成的那个结果,其它的异步任务虽然也会继续进行下去,不过race已经不管那些任务的结果了。
race() 后面的 .then 只会执行第一个完成的 promise 回调,其他promise 仍继续 但不会再进入到 .then

function runAsync(x) {
  const p = new Promise(r =>
    setTimeout(() => r(x, console.log(x)), 1000)
  );
  return p;
}
function runReject(x) {
  const p = new Promise((res, rej) =>
    setTimeout(() => rej(`Error: ${x}`, console.log(x)), 1000 * x)
  );
  return p;
}
Promise.race([runReject(0), runAsync(1), runAsync(2), runAsync(3)])
  .then(res => console.log("result: ", res))
  .catch(err => console.log(err));



// ----------------answer-------------
0
Error: 0
1
2
3

runReject(0) 最先执行完,抛出错误 rej(Error: 0) 所以进入了 catch() 中,Promise.race().catch执行完成,将不再执行,而 runAsync(1), runAsync(2), runAsync(3) 仍继续执行。

Promise.any()

Promise.any() 返回一组异步任务中最快的成功结果,如果全部失败就返回失败结果。

接收一个数组,数组的每一项都是一个promise对象,该方法会返回一个新的 promise,数组内的任意一个 promise 变成了resolved状态,那么由该方法所返回的 promise 就会变成resolved状态;
如果数组内的 promise 状态都是rejected,那么该方法所返回的 promise 就会变成rejected状态

all、race、any的区别

all: 成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值
race: 哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
any: 返回最快的成功结果,如果全部失败就返回失败结果。

async/await

async/await 是 Generator的语法糖,对异步操作的一种封装,用同步方式,执行异步操作

async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成,用同步的方法执行异步操作。

function foo(num) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(num * 2)
        }, 1000)
    })
}
async function asyncFn() {
    const num1 = await foo(1)
    const num2 = await foo(num1)
    const num3 = await foo(num2)
    return num3
}
asyncFn().then(res => console.log(res))

async

async 声明一个异步函数,返回的是一个Promise对象,async 函数内部 return 的返回值,会成为 then 回调函数的参数。

async 是位于 function 前面的一个前缀,只有 async 函数中,才能使用 await。
async 执行完之后会返回一个 promise 的对象:

  • 若返回值刚好是一个 promise 对象则直接返回;
  • 若返回值不是 Promise 对象,会将对应的结果包装成 promise 对象,相当于 Promise.resolve();
  • 若没有返回值则会返回 undefined 的 promise 对象 。

因此 async 修饰的函数还能继续使用 .then 进行链式调用。

async function fn () {
  // return await 1234
  // 等同于
  return 123
}
fn().then(res => console.log(res))   // 123

await

await 后面跟着的是 promise 的异步操作,await 会阻塞后面的代码,直到 promise 的完成并返回其处理结果,await 后面

  • 可以是普通值
  • 可以是 thenable
  • 也可以是 Promise 主动调用 resolve 或者 reject

当 promise 的状态变为 resolved 时才会执行 await 后续的代码;

function request(num) { // 模拟接口请求
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(num * 2)
    }, 1000)
  })
}


async function fn () {
  const res1 = await request(5)
  const res2 = await request(res1)
  console.log(res2) // 2秒后输出 20
}
fn()

等 request(5) 执行完之后再继续下一个 request(res1) 的请求,在 async 函数中,await 规定了异步操作只能一个一个排队执行,从而达到 **用同步方式,执行异步操作 **的效果。

如果 await 后面跟的不是 promise ,await 后面接的不是 Promise 的话,会通过 promise.resolve() 将其转换成 Promise。

function request(num) { // 去掉Promise
  setTimeout(() => {
    console.log(num * 2)
  }, 1000)
}

async function fn() {
  await request(1) // 2
  await request(2) // 4
}
fn()

// 1秒后执行完  同时输出 2 4


而本案例的 setTimeout 失去了排队的效果 是因为 async/await 本身是会串行执行微任务的,

错误处理

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

需要捕获 rejected 则需要在函数内部 使用 try catch 或者链式调用 .catch 操作。

async/await

使用语法:

  1. 函数前面使用 async 修饰
  2. async 函数内部 用 await 来修饰 promise 操作
  3. **await只能在async函数中使用 **不然会报错

作用:用同步方式,执行异步操作

async/await 实现同步执行异步操作

async function asyncFn() {
  const num1 = await fn(1)
  console.log(num1) // 2
  const num2 = await fn(num1)
  console.log(num2) // 4
  const num3 = await fn(num2)
  console.log(num3) // 8
  return num3
}
const asyncRes = asyncFn()
console.log(asyncRes) // Promise
asyncRes.then(res => console.log(res)) // 8


手写实现

Promise 实现原理

const promise = new Promise((resolve, reject) => {
   resolve('success')
   reject('err')
})

promise.then(value => {
  console.log('resolve', value)
}, reason => {
  console.log('reject', reason)
})

// 输出 resolve success

通过以上例子可以分析出 promise 的实现:

  • promise 是一个类,实例化 promise 时传入执行器,通过执行对应的函数完成 promise 的状态转变:
    • promise的 初始状态为pending;
    • 执行 resolve 转变为 resolved(fulfilled);
    • 执行 reject 状态转变为 rejected;
    • 执行过程中抛出错误 throw error时状态转变为 rejected。
  • 状态发生一旦变化则不会再改变,一次即永久。
  • then 的链式调用,接收 promise 的最终结果,通过状态判断需要执行的回调函数
    • 如果状态成功,则调用成功的回调函数
    • 如果状态失败,则调用失败的回调函数

resolve、reject的实现

  1. 新建 MyPromise 初始化状态传入执行器

promise 的状态、 promise最终结果
执行器 executor:(resolve, reject) => {}

class MyPromise{
  constructor(executor){
    // 1. 初始化值
    this.promiseStatus = 'pending'    //  promiseStatus 的状态,初始化时为 pending 
    this.promiseResult = null     //  promise 的结果
		executor() // 接受实例化 MyPromise 时传入的执行函数,会立即执行
  }
}

  1. 向执行器中传入 resolve、reject

在函数 resolve reject 修改 promise 的状态,并修改变化后的值,而在调用 resolve / reject 时要确定 this 的指向。
如果 执行器中 直接调用 this.resolve() 的话,resolve函数(普通函数) 中的 this 指向的是 window 或者undefined。
为了防止随着函数执行环境的变化而变化,要将resolve、reject函数中 this 指向 MyPromise 实例。
方法一: 通过 .bind(this)

class MyPromise{
  constructor(executor){
    // 1. 初始化值
    this.promiseStatus = 'pending'    //  promiseStatus 的状态,初始化时为 pending 
    this.promiseResult = null     //  promise 的结果

    // 2. 初始化 resolve、reject 的 this 指向
    //  resolve 和 reject 的 this 要指向永远指向当前的 MyPromise 实例, 否则 resolve 中的 this丢失
    //  通过 bind(this) 将函数的 this 指向实例,防止随函数执行环境的变化而变化 
    //  或者将 resolve、 reject 函数写成箭头函数的方式
    this.resolve = this.resolve.bind(this)
    this.reject = this.reject.bind(this)
    executor(this.resolve,this.reject) // 接受实例化 MyPromise 时传入的执行函数,会立即执行
  }

  // 如果直接调用的话,普通函数this指向的是window或者undefined
  // 所以在构造器中通过 bind(this) 改变 this 指向
  resolve(value) {
    // 如果没有 bind(this), 函数内部的 this 会丢失为 undefined 
    // console.log(this);  
    this.promiseStatus = 'resolved'
    this.promiseResult = value
  }

  reject(reason) {
    this.promiseStatus = 'rejected'
    this.promiseResult = reason
  }
}

方法二:采用 回调函数的形式 让 函数中的 this 指向当前实例对象

class MyPromise{
  constructor(executor){
    // 1. 初始化值
    this.promiseStatus = 'pending'    //  promiseStatus 的状态,初始化时为 pending 
    this.promiseResult = null     //  promise 的结果

    executor(this.resolve,this.reject) // 接受实例化 MyPromise 时传入的执行函数,会立即执行
  }


  // 用箭头函数就可以让this指向当前实例对象
  resolve=(value)=>{
    this.promiseStatus = 'resolved'
    this.promiseResult = value
  }

  reject=(reason)=> {
    this.promiseStatus = 'rejected'
    this.promiseResult = reason
  }
}


此时可以测试一下 MyPromise 的实现是否成功

const test1 = new MyPromise((resolve,reject)=>{
  resolve('success!')
  reject('error!')
})
console.log('test1 ===>> ',test1);

运行代码情况如下:
![image.png](https://img-blog.csdnimg.cn/img_convert/f2655548f142ae012f8a67cc4381a8df.png#averageHue=#212120&clientId=ua9c0d15b-857c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=135&id=u42eb4b71&margin=[object Object]&name=image.png&originHeight=203&originWidth=696&originalType=binary&ratio=1&rotation=0&showTitle=false&size=20484&status=done&style=none&taskId=u69138273-167e-4e91-926a-4520b1e43e2&title=&width=464)
而我们发现此时的状态为 rejected ,并不是第一个 resolve 成功的结果, 说明并没有保证状态变更的唯一性

  1. 保证 promise 的状态不可变

为了确保 promise 的状态只变更一次,则要在 resolve 中加以限制,只有在状态为pending 是才能发生改变。

resolve(value) {
  if(this.promiseStatus === 'pending'){
    this.promiseStatus = 'resolved'
    this.promiseResult = value
  }
 
}

reject(reason) {
  if(this.promiseStatus === 'pending'){  
    this.promiseStatus = 'rejected'
    this.promiseResult = reason
  }
}
  1. throw

而当执行器中 抛出异常时该如何捕获呢?
其实很简单只要在 executor 处使用 try/catch 即可,当抛出异常时使用 reject() 改变 promise 的状态

try{
  executor(this.resolve,this.reject) // 接受实例化 MyPromise 时传入的执行函数,会立即执行
} catch(e){
  // 当捕获到错误时执行 reject
  this.reject(e)
}

then 的实现

then 的使用方法

promise.resolve(1).then(res=>{
    console.log(res);
    //在构造函数中如果你执行力resolve函数就会到这一步
},err=>{
    // 执行了reject函数会到这一步
    console.log(err);
})


then 的基本原理:

  • 在使用 then 使通常提供两个回调,一个成功的回调,一个失败的回调
  • 通过 promise 的状态来选择当前调用的回调;
  • 如果 resolve、reject 中包含定时器,则在定时器结束后执行 then;
  • then 返回的是一个新的 promise包装对象,由此实现了 then 的链式调用
then 的简单实现

接收两个回调,并通过 promise 的状态选择执行的回调

then(onFulfilled, onRejected) {
  // 判断状态
  if (this.promiseStatus === 'resolved') {
    // 调用成功回调,并且把值返回
    onFulfilled(this.promiseResult);
  } else if (this.promiseStatus === 'rejected') {
    // 调用失败回调,并且把原因返回
    onRejected(this.promiseResult);
  }
}
定时器

当我们在 resolve 中遇到定时器时,then 会怎么处理呢?

const test = new Promise((resolve, reject) => {
  setTimeout(() => {
      resolve('成功') // 1秒后输出 成功
  }, 1000)
}).then(res => console.log('resolve',res), err => console.log('reject',err))

我们不能确保 1秒 后才执行 then 函数,但是我们可以保证 1秒 后再执行 then 里的回调。

在遇到定时器的时候我们要将 then 的两个回调函数保存起来,当 promise 的状态发生改变时再去从数组中取出对应的回调,并根据 promise 的状态判断执行哪个回调。
由于 then 支持链式调用,所以采用数组的形式来保存每一次的回调函数

class MyPromise{
  constructor(executor){
    // 1. 初始化值
    this.promiseStatus = 'pending'    //  promiseStatus 的状态,初始化时为 pending 
    this.promiseResult = null     //  promise 的结果

    // 2. 初始化 resolve、reject 的 this 指向
    //  resolve 和 reject 的 this 要指向永远指向当前的 MyPromise 实例, 否则 resolve 中的 this丢失
    //  通过 bind(this) 将函数的 this 指向实例,防止随函数执行环境的变化而变化 
    //  或者将 resolve、 reject 函数写成箭头函数的方式
    this.resolve = this.resolve.bind(this)
    this.reject = this.reject.bind(this)
    
    //  then 的链式调用 并支持 定时器
    this.onResolvedCallbacks = []     // 保存成功的回调
    this.onRejectedCallbacks = []   // 保存失败的回调

    try{
      executor(this.resolve,this.reject) // 接受实例化 MyPromise 时传入的执行函数,会立即执行
    } catch(e){
      // 当捕获到错误时执行 reject
      this.reject(e)
    }
  }


  // 如果直接调用的话,普通函数this指向的是window或者undefined
  // 所以在构造器中通过 bind(this) 改变 this 指向
  resolve(value) {

    // 如果没有 bind(this), 函数内部的 this 会丢失为 undefined 
    // console.log(this);  

    // 保证只有在 pending 状态时才能发生改变
    if(this.promiseStatus === 'pending'){
      this.promiseStatus = 'resolved'
      this.promiseResult = value
    }

    
    while (this.onResolvedCallbacks.length) {
      this.onResolvedCallbacks.shift()(this.promiseResult)
    }
    
  }

  reject(reason) {

    // 保证只有在 pending 状态时才能发生改变
    if(this.promiseStatus === 'pending'){  
      this.promiseStatus = 'rejected'
      this.promiseResult = reason
    }

    while (this.onRejectedCallbacks.length) {
        this.onRejectedCallbacks.shift()(this.promiseResult)
    }
  }


  then(onFulfilled, onRejected) {
    console.log(onFulfilled)
    // 判断状态
    if (this.promiseStatus === 'resolved') {
      // 调用成功回调,并且把值返回
      onFulfilled(this.promiseResult);
    } else if (this.promiseStatus === 'rejected') {
      // 调用失败回调,并且把原因返回
      onRejected(this.promiseResult);
    }else if (this.promiseStatus === 'pending') {
      // 如果状态为待定状态, 表示执行器还未执行完毕,暂时保存两个回调
      this.onResolvedCallbacks.push(onFulfilled.bind(this))
      this.onRejectedCallbacks.push(onRejected.bind(this))
    }

  }
}




const test = new MyPromise((resolve, reject) => {
  setTimeout(() => {
      resolve('成功') // 1秒后输出 成功
  }, 1000)
}).then(res => console.log('resolve',res), err => console.log('reject',err))



链式调用

then 能实现链式调用的根本原因是它的返回对象:

  • then 返回的是一个新的 promise 对象
  • 如果返回值是 promise 对象,返回值为成功,新 promise 就是成功
  • 如果返回值是 promise 对象,返回值为失败,新 promise 就是失败
  • 如果返回值非 promise 对象,新 promise 对象就是成功,值为此返回值
  • then 不能返回自身,否则会造成死循环
 //  then 返回的是一个 promise 对象
 then(onFulfilled, onRejected) {

  var thenPromise = new MyPromise((resolve, reject) => {
    // 将参数 resolvePromise 赋值为一个函数
    const resolvePromise = cb => {
      try {
          const x = cb(this.promiseResult)
          if (x === thenPromise) {
              // 不能返回自身哦
              throw new Error('不能返回自身。。。')
          }
          if (x instanceof MyPromise) {
              // 如果返回值是Promise
              // 如果返回值是promise对象,返回值为成功,新promise就是成功
              // 如果返回值是promise对象,返回值为失败,新promise就是失败
              // 谁知道返回的promise是失败成功?只有then知道
              x.then(resolve, reject)
          } else {
              // 非Promise就直接成功
              resolve(x)
          }
      } catch (err) {
          // 处理报错
          reject(err)
          throw new Error(err)
      }
    }

    // 判断状态
    if (this.promiseStatus === 'resolved') {
      // 此时执行 resolvePromise() 并将成功的回调作为参数传入
      resolvePromise(onFulfilled)
    } else if (this.promiseStatus === 'rejected') {
      // 执行 resolvePromise() 并将失败的回调作为参数传入
      resolvePromise(onRejected)
    }else if (this.promiseStatus === 'pending') {
      // 如果状态为待定状态,暂时保存两个回调
      this.onResolvedCallbacks.push(onFulfilled.bind(this))
      this.onRejectedCallbacks.push(onRejected.bind(this))
    }

  })

  return thenPromise
}

验证链式调用是否成功

// 链式调用 输出300
const p = new MyPromise((resolve, reject) => {
  resolve(100)
}).then(res => new Promise((resolve, reject) => resolve(3 * res)), err => console.log(err))
  .then(res => console.log(res), err => console.log(err))
微任务

在 js 的运行机制中 then 是微任务。当遇到微任务时,将微任务放入任务队列排队,等当前的宏任务执行完毕再去任务队列中取出微任务执行。
then 身为微任务的特质,此时在 MyPromise 中该如何实现呢? 还是得通过定时器解决该问题。
只需要让 resolvePromise 函数异步执行就可以了


   then(onFulfilled, onRejected) {
    var thenPromise = new MyPromise((resolve, reject) => {

      // resolvePromise 接收一个 函数
      const resolvePromise = cb => {
        setTimeout(() => {
          try {
              const x = cb(this.promiseResult)
              if (x === thenPromise) {
                  // 不能返回自身哦
                  throw new Error('不能返回自身。。。')
              }
              if (x instanceof MyPromise) {
                  // 如果返回值是Promise
                  // 如果返回值是promise对象,返回值为成功,新promise就是成功
                  // 如果返回值是promise对象,返回值为失败,新promise就是失败
                  // 谁知道返回的promise是失败成功?只有then知道
                  x.then(resolve, reject)
              } else {
                  // 非Promise就直接成功
                  resolve(x)
              }
          } catch (err) {
              // 处理报错
              reject(err)
              throw new Error(err)
          }
        })
      }
    
      // 判断状态
      if (this.promiseStatus === 'resolved') {
        // 执行 resolvePromise 并将成功的回调作为参数传入
        resolvePromise(onFulfilled)
      } else if (this.promiseStatus === 'rejected') {
        // 将失败的回调传入 resolvePromise 执行
        resolvePromise(onRejected)
      }else if (this.promiseStatus === 'pending') {
        // 如果状态为待定状态,暂时保存两个回调
        this.onResolvedCallbacks.push(onFulfilled.bind(this))
        this.onRejectedCallbacks.push(onRejected.bind(this))
      }
    
    })
   return thenPromise
  }
const p = new MyPromise((resolve, reject) => {
  resolve(1)
}).then(res => console.log(res), err => console.log(err))

console.log(2)

// -----------------------------------------
2
1

all

  • 接收一个 Promise 数组,数组中如有非 Promise 项,则此项当做成功

  • 如果所有 Promise 都成功,则返回成功结果数组

  • 如果有一个 Promise 失败,则返回这个失败结果

static all(promises) {
    const result = []
    let count = 0
    return new MyPromise((resolve, reject) => {
        const addData = (index, value) => {
            result[index] = value
            count++
            if (count === promises.length) resolve(result)
        }
        promises.forEach((promise, index) => {
            if (promise instanceof MyPromise) {
                promise.then(res => {
                    addData(index, res)
                }, err => reject(err))
            } else {
                addData(index, promise)
            }
        })
    })
} 

race

  • 接收一个 Promise 数组,数组中如有非 Promise 项,则此项当做成功
  • 哪个 Promise 最快得到结果,就返回那个结果,无论成功失败
static race(promises) {
    return new MyPromise((resolve, reject) => {
        promises.forEach(promise => {
            if (promise instanceof MyPromise) {
                promise.then(res => {
                    resolve(res)
                }, err => {
                    reject(err)
                })
            } else {
                resolve(promise)
            }
        })
    })
}

allSettled

  • 接收一个Promise数组,数组中如有非Promise项,则此项当做成功
  • 把每一个Promise的结果,集合成数组,返回
static allSettled(promises) {
    return new Promise((resolve, reject) => {
        const res = []
        let count = 0
        const addData = (status, value, i) => {
            res[i] = {
                status,
                value
            }
            count++
            if (count === promises.length) {
                resolve(res)
            }
        }
        promises.forEach((promise, i) => {
            if (promise instanceof MyPromise) {
                promise.then(res => {
                    addData('fulfilled', res, i)
                }, err => {
                    addData('rejected', err, i)
                })
            } else {
                addData('fulfilled', promise, i)
            }
        })
    })
}

any

any 与 all 相反

  • 接收一个 Promise 数组,数组中如有非 Promise 项,则此项当做成功
  • 如果有一个 Promise 成功,则返回这个成功结果
  • 如果所有 Promise 都失败,则报错
static any(promises) {
    return new Promise((resolve, reject) => {
        let count = 0
        promises.forEach((promise) => {
            promise.then(val => {
                resolve(val)
            }, err => {
                count++
                if (count === promises.length) {
                    reject(new AggregateError('All promises were rejected'))
                }
            })
        })
    })
}
}

async/await 实现原理

上文我们说到 async/await 是 基于 generator 实现的语法糖,那么先来看一看究竟什么是 generator:

什么是 generator?

Generator 函数是 ES6 提供的一种异步编程解决方案,可以把 Generator 理解成一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个遍历器对象该对象可以依次遍历 Generator 内部的每一个状态,为改变执行流程提供了可能,从而为异步编程提供解决方案

generator 与普通函数的区别是多了一个 * 号,并且只有在 generator 函数中才能使用 yield,当执行到 yield 时generator 的执行流会被挂起,通过 next() 方法能让 generator 切换到下一个状态,next() 执行后会返回一个对象,其中包含两个属性 value 和 done

  • value: 暂停点后面接的值,也就是yield后面接的值
  • done:是否generator函数已走完,没走完为false,走完为true
generator 基本用法
 function* gen() {
  yield 1
  yield 2
  yield 3
}
const g = gen()
console.log(g.next()) // { value: 1, done: false }
console.log(g.next()) // { value: 2, done: false }
console.log(g.next()) // { value: 3, done: false }
console.log(g.next()) // { value: undefined, done: true }
yield 函数时 next 返回值

当 generator 暂停点 yield 后接的是一个函数时,**马上执行该函数并将函数的返回值 **作为 yield 的对象 value 值
当 yield 后函数的返回值为 Promise 时,会将当前状态为 pending 的 promise 对象 作为 yield 的 value 值

function fn(num) {
  return new Promise(resolve => {
    console.log('执行resolve')
    setTimeout(() => {
      console.log('执行setTimeout')
      resolve(num)
    }, 1000)
  })
}
function* gen() {
  yield fn(1)
  yield fn(2)
  return 3
}
const g = gen()
console.log(g.next().value) 
console.log(g.next().value) 
console.log(g.next()) 

在执行 gen.next() 时会在将 yeild 之后的函数执行的返回值作为暂停点对象的value值
所以此时的执行结果为:
![image.png](https://img-blog.csdnimg.cn/img_convert/d7a8293a2fb1cee3481f8630d2059b71.png#averageHue=#fdfdfc&clientId=u81e94d19-a8f9-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=149&id=ud376af1e&margin=[object Object]&name=image.png&originHeight=143&originWidth=289&originalType=binary&ratio=1&rotation=0&showTitle=false&size=7502&status=done&style=none&taskId=ub9d976c1-c6cb-402d-be97-7e912d8a85b&title=&width=300.20001220703125)
fn() 返回了一个promise,而 promise 的状态在定时器中才发生变更,所以前两个的 value 都是实例化时 promise 的状态 pending

若此时没有l定时器的干扰,promise 的状态会发生什么样的而变化呢?

function fn(num) {
  return new Promise(resolve => {
    console.log('执行resolve')
    resolve(num)
  })
}
function* gen() {
  yield fn(1)
  yield fn(2)
  return 3
}
const g = gen()
console.log(g.next().value) 
console.log(g.next().value) 
console.log(g.next()) 

实例化 promise 时会执行resolve 回调,此时 next 返回的 promise 状态会从 pending 转变为 fulfilled (resolved)
![image.png](https://img-blog.csdnimg.cn/img_convert/0fed569e9f5456b832988c2ac31e62c5.png#averageHue=#fefdfc&clientId=u81e94d19-a8f9-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=122&id=ub73475f3&margin=[object Object]&name=image.png&originHeight=127&originWidth=333&originalType=binary&ratio=1&rotation=0&showTitle=false&size=7218&status=done&style=none&taskId=u940c16c4-3fe5-448b-9e32-1ffcbd40fc2&title=&width=320.3999938964844)

next 传参

generate 可以用 next() 传递参数,通过 yield 来接收传递的值:

  • 第一个 next() 传参无效,只有第二个开始传参才会生效
  • 当 next 传入参数在上一次暂停的 yield 接收,在遇到下一个 yield 时会将 yield 后的返回值作为暂停点的对象返回
function* gen() {
  const num1 = yield 1
  console.log(num1)
  const num2 = yield 2
  console.log(num2)
  return 3
}
const g = gen()
console.log(g.next()) // { value: 1, done: false }
console.log(g.next(11111))
// 11111
//  { value: 2, done: false }
console.log(g.next(22222)) 
// 22222
// { value: 3, done: true }

此时输出的结果为

{value: 1, done: false}   // 第一个 next 返回的 yield 1 的对象 
1 11111									  // 第二个 next 执行时传入 11111 被第一个 yield 接收并赋值给num1, 此时 num1 = 11111
{value: 2, done: false}   // yield 2 时 返回的对象 
22222                     // 第三个 next 传入 22222时 yield 接收参数并赋值给 num2, num2 = 22222
{value: 3, done: true}    // generator return 3

Promise + next 传参
当我们 Promise 与 next 传参相结合是会发生什么情况呢?

function fn(nums) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(nums * 2)
    }, 1000)
  })
}
function* gen() {
  const num1 = yield fn(1)   // 调用 g.next(res1)时 传入的参数会被 yield 接收 并赋值给 num1
  const num2 = yield fn(num1)   
  const num3 = yield fn(num2)
  return num3
}
const g = gen()
const next1 = g.next()
next1.value.then(res1 => {
  console.log(next1) // 1秒后同时输出 { value: Promise { 2 }, done: false }
  console.log(res1) // 1秒后同时输出 2

  const next2 = g.next(res1) // 传入上次的res1 2
  next2.value.then(res2 => {
    console.log(next2) // 2秒后同时输出 { value: Promise { 4 }, done: false }
    console.log(res2) // 2秒后同时输出 4

    const next3 = g.next(res2) // 传入上次的res2
    next3.value.then(res3 => {
      console.log(next3) // 3秒后同时输出 { value: Promise { 8 }, done: false }
      console.log(res3) // 3秒后同时输出 8

       // 传入上次的res3
      console.log(g.next(res3)) // 3秒后同时输出 { value: 8, done: true }
    })
  })
})


基于 generator 实现 async/await

async/await 的实现原理:

  • async/await 自带执行器,不需要手动调用 next() 就能自动执行下一步
  • async 函数返回值是 Promise 对象,而 Generator 返回的是生成器对象
  • await 能够返回 Promise 的 resolve/reject 的值

generator 函数的 Promise+next 传参,就很像 async/await 了,区别在于

  • gen 函数执行返回值不是 Promise,asyncFn 执行返回值是 Promise
  • gen 函数需要执行相应的操作,才能等同于 asyncFn 的排队效果
  • gen 函数执行的操作是不完善的,因为并不确定有几个 yield,不确定会嵌套几次
function generatorToAsync(generatorFn) {
  return function() {
    const gen = generatorFn.apply(this, arguments) // gen有可能传参

    // 返回一个Promise
    return new Promise((resolve, reject) => {

      function go(key, arg) {
        let res
        try {
          res = gen[key](arg) // 执行 next(), 获取执行返回的对象,有可能是reject状态的Promise
        } catch (error) {
          return reject(error) // 报错的话会走catch,直接reject
        }

        // 解构获得value和done
        const { value, done } = res
        if (done) {
          // 如果done为true,说明走完了,进行resolve(value)
          return resolve(value)
        } else {
          // 如果done为false,说明没走完,还得继续走

          // value有可能是:常量,Promise,Promise有可能是成功或者失败
          return Promise.resolve(value).then(val => go('next', val), err => go('throw', err))
        }
      }

      go("next") // 第一次执行
    })
  }
}

使用 generatorToAsync函数 的版本

function* gen() {
  const num1 = yield fn(1)
  console.log(num1) // 2
  const num2 = yield fn(num1)
  console.log(num2) // 4
  const num3 = yield fn(num2)
  console.log(num3) // 8
  return num3
}

const genToAsync = generatorToAsync(gen)
const asyncRes = genToAsync()
console.log(asyncRes) // Promise
asyncRes.then(res => console.log(res)) // 8

参考

  1. 什么是回调地狱,如何用Promise解决回调地狱
  2. 45道Promise面试题一次爽到底
  3. async/await 优雅永不过时
  4. 手写Promise原理,最通俗易懂的版本
  5. 20分钟就能搞定的async/await原理

你可能感兴趣的:(javascript,前端,开发语言)