前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >手写系列-这一次,彻底搞懂 Promise

手写系列-这一次,彻底搞懂 Promise

作者头像
游魂
发布2023-10-17 15:57:20
2380
发布2023-10-17 15:57:20
举报
文章被收录于专栏:前端开发前端开发
img
img

一、前言

想要实现 Promise,必须先了解 Promise 是什么,以及 Promise 有哪些功能。

还不是特别了解 Promise 的同学,建议先移步 ES6入门-Promise 熟悉。

Promise 是基于 Promises/A+ 规范 实现的,换句话说,我们可以按照 Promises/A+ 规范 来手写 Promise。

1.1 小例子

Promise,直译过来就是承诺,Promise 到底承诺了什么呢?

当我在麦当劳点一份汉堡套餐,收银员会给我一张收据,这个收据就是 Promise,代表我已经付过钱了,麦当劳会为我做一个汉堡套餐的承诺,我要通过收据来取这个汉堡套餐。

那么这个买汉堡得到的承诺会有以下 3 种状态:

  1. 等待状态:我刚下单,汉堡还没做好,这时我可以在等待汉堡时,同时做其他事情;
  2. 成功状态:汉堡做好了,通知我取餐;
  3. 失败状态:发现卖完了,通知我退款;

需要注意的是,状态的修改是不可逆的,当汉堡做好了,承诺兑现了,就不能再回到等待状态了。

总结一下,Promise 就是一个承诺,承诺会给你一个处理结果,可能是成功的,可能是失败的,而返回结果之前,你可以同时做其他事情。

二、Promises/A+

接下来,按照 Promises/A+ 规范 一步步实现 Promise。

1. Promise 基本用法

先瞅一眼 ES6 Promise 基本用法。

代码语言:javascript
复制
const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value)
  } else {
    reject(error)
  }
});

promise.then(data => {
    console.log('请求成功')
}, err => {
    console.log('请求失败')
})

1.1 Promise 状态

Promise 拥有自己的状态,初始状态->成功状态时,执行成功回调,初始状态->失败状态时,执行失败回调。

  1. pending:初始状态,可以转换为 fulfilledrejected 状态;
  2. fulfilled:成功状态,转换到该状态时必须有成功返回值,且不能再次转换状态;
  3. rejected:失败状态,转换到该状态时必须有错误原因,且不能再次转换状态;

通过已知的 Promise 3 种状态,可定义常量 STATUS 和 MyPromise 状态 status。

代码如下:

代码语言:javascript
复制
// Promise 3 种状态
const STATUS = {
  PENDING: 'pending',
  FULFILLED: 'fulfilled',
  REJECTED: 'rejected'
}

class MyPromise {
    // 初始状态为 pending
    status = STATUS.PENDING
}

1.2 执行器

从基本用法可知,Promise 需要接收 1 个执行器函数作为参数,这个函数带有 2 个参数。

  1. resolve:把 Promise 状态改成成功;
  2. reject:把 Promise 状态改成失败;

代码如下:

代码语言:javascript
复制
class MyPromise {
  constructor (executor) {
      // 执行器
      executor(this.resolve, this.reject)
  }
  // 成功返回值
  value = null

  // 失败返回值
  reason = null

  // 修改 Promise 状态,并定义成功返回值
  resolve = value => {
      if (this.status === STATUS.PENDING) {
          this.status = STATUS.FULFILLED
          this.value = value
      }
  }

  // 修改 Promise 状态,并定义失败返回值
  reject = reason => {
      if (this.status === STATUS.PENDING) {
              this.status = STATUS.REJECTED
              this.reason = reason
          }
      }
    }
}

1.3 then

Promise 拥有 then 方法,then 方法第一个参数是成功状态的回调函数 onFulfilled,第二个参数是失败状态的回调函数 onRejected

代码语言:javascript
复制
promise.then(onFulfilled, onRejected)

onFulfilled 要求如下:

  • 必须在 promise 状态为完成时调用它,并将 promise 的 value 作为它的第一个参数;
  • 在 promise 完成之前不能调用它;
  • 它不能被多次调用;

onRejected 要求如下:

  • 必须在 promise 被拒绝后调用它,以 promise.reason 作为它的第一个参数;
  • 在 promise 被拒绝之前不能调用它;
  • 它不能被多次调用;

代码如下:

代码语言:javascript
复制
class MyPromise {
    then = function (onFulfilled, onRejected) {
      if (this.status === STATUS.FULFILLED) {
          onFulfilled(this.value)
      } else if (this.status === STATUS.REJECTED) {
          onRejected(this.reason)
      }
    }
}

1.4 试试看

按照 Promise 的基本用法,创建 MyPromise 实例 mypromise。

代码语言:javascript
复制
const mypromise = new MyPromise((resolve, reject) => {
  resolve('成功')
})

mypromise.then(data => {
  console.log(data, '请求成功') // 成功打印“成功 请求成功”
}, err => {
  console.log(err, '请求失败')
})

2. Promise.then

下文将按照 Promises/A+ 规范 完善 MyPromise.then 方法。

Promises/A+ 规范 中标明 then 有以下要求:

1. 可选参数

onFulfilled、onRejected 是可选参数。

  • 如果 onFulfilled 不是函数,则必须忽略它;
  • 如果 onRejected 不是函数,则必须忽略它;

代码如下:

代码语言:javascript
复制
class MyPromise {
    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
        onRejected = typeof onRejected === 'function' ? onRejected : error => { throw error }
    }
}

2. 多次调用 then

then 可以在同一个承诺上多次调用。

  • 当 promise 完成,所有相应的 onFulfilled 回调必须按照它们的原始调用的顺序执行 then;
  • 当 promise 被拒绝,所有相应的 onRejected 回调必须按照它们对 的原始调用的顺序执行 then;
2.1 数组缓存回调

可以理解为将 onFulfilled、onRejected 作为数组存储在 MyPromise 中,然后按照顺序执行。

代码如下:

代码语言:javascript
复制
class MyPromise {
  // 成功回调
  onFulfilledCallback = []

  // 失败回调
  onRejectedCallback = []

  // 修改 Promise 状态,并定义成功返回值
  resolve = value => {
    if (this.status === STATUS.PENDING) {
        this.status = STATUS.FULFILLED
        this.value = value

        while(this.onFulfilledCallback.length) {
            this.onFulfilledCallback.shift()(value)
        }
    }
  }

  // 修改 Promise 状态,并定义失败返回值
    reject = reason => {
        if (this.status === STATUS.PENDING) {
            this.status = STATUS.REJECTED
            this.reason = reason

            while(this.onRejectedCallback.length) {
                this.onRejectedCallback.shift()(reason)
            }
        }
    }

    then = function (onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
        if (this.status === STATUS.PENDING) {
          this.onFulfilledCallback.push(onFulfilled)
          this.onRejectedCallback.push(onRejected)
        } else if (this.status === STATUS.FULFILLED) {
            onFulfilled(this.value)
        } else if (this.status === STATUS.REJECTED) {
            onRejected(this.reason)
        }
    }
}

由此,我们已实现了一个基础的 Promise。

2.2 试试看

看了这么久,试一试 MyPromise 是否符合要求吧。

代码如下:

代码语言:javascript
复制
const mypromise = new MyPromise((resolve, reject) => {
  resolve('成功')
})

mypromise.then(data => {
  console.log(data, '1')
})

mypromise.then(data => {
  console.log(data, '2')
})

输出结果如图:

image-20230712140720270
image-20230712140720270

由图可知,和预期一样。

3. 链式调用 then

then 必须返回一个 Promise 来支持链式调用 Promise。

示例代码如下:

代码语言:javascript
复制
mypromise.then(data => {
  console.log(data, '请求成功')
  return '2'
}).then(data => {
  console.log(data, '请求成功')
  return '3'
})
3.1 改写 then 方法

改动点如下:

  • then 方法需要返回 MyPromise 实例;
  • then 内部调用回调时,需通过 resolvePromise 方法判断返回值 x 的类型来处理返回值。
代码语言:javascript
复制
class MyPromise {
  then = function (onFulfilled, onRejected) {
    // 返回 MyPromise实例
    const promise2 = new MyPromise((resolve, reject) => {
      if (this.status === STATUS.PENDING) {
        this.onFulfilledCallback.push(() => {
          const x = onFulfilled(this.value)
          resolvePromise(promise2, x, resolve, reject)
        })
        this.onRejectedCallback.push(() => {
          const x = onRejected(this.reason)
          resolvePromise(promise2, x, resolve, reject)
        })
      } else if (this.status === STATUS.FULFILLED) {
        const x = onFulfilled(this.value)
        resolvePromise(promise2, x, resolve, reject)
      } else if (this.status === STATUS.REJECTED) {
        const x = onRejected(this.error)
        resolvePromise(promise2, x, resolve, reject)
      }
    }) 

    return promise2
  }
}

上述代码引用了 resolvePromise 来处理 Promise.then 的返回值

3.1.1 注意

这里的promise2暂时还不能正常运行,可能会报错:Cannot access 'promise2' before initialization | 不能访问 promise2 在初始化之前。

原因:在 new promise 时,promise2 还没有完成初始化。所以 resolvePromise 中不能访问到 promise2,在当前的执行上下文栈中,onFulfilled 或 onRejected 是不能被直接调用的,onFulfilled 或 onRejected 得是在当前事件循环后异步执行的。

解决方法:可以使用 setTimeout、setImmediate、MutationObserever、process.nextTick在 then 方法被调用后将创建一个新的栈,这个我们后续处理,先正常往下看。

3.2 resolvePromise

Promises/A+ 规范 对resolvePromise 的要求如下:

image-20230712141403824
image-20230712141403824
  • 如果 promise2 === x, 执行 reject,错误原因为 TypeError
  • 如果 x 是函数或对象
    • 如果 x.then 是函数
      • 执行 x.then
    • 如果 x.then 不是函数
      • 执行 resolve(x)
  • 如果 x 不是函数或对象
    • 执行 resolve(x)

代码如下:

代码语言:javascript
复制
function resolvePromise (promise2, x, resolve, reject) {
  // 如果 promise2 === x, 执行 reject,错误原因为 TypeError
    if (promise2 === x) {
      reject(new TypeError('The promise and the return value are the same'))
    }

    // 如果 x 是函数或对象
    if ((typeof x === 'object' && x != null) || typeof x === 'function') {
      let then
      try {
        then = x.then
      } catch (error) {
        reject(error)
      }

      // 如果 x.then 是函数
      if (typeof then === 'function') {
        // Promise/A+ 2.3.3.3.3 只能调用一次
        let called = false
        try {
          then.call(x, y => {
            // resolve的结果依旧是promise 那就继续解析 | 递归解析
            if (called) return
            called = true
            resolvePromise(promise2, y, resolve, reject)
          }, err => {
            if (called) return
            called = true
            reject(err)
          })
        } catch (error) {
          if (called) return
          reject(error)
        }
      } else {
          // 如果 x.then 不是函数
          resolve(x)
      }
    } else {
        // 如果 x 不是 promise 实例
        resolve(x)
    }
}
3.3 试一试

试试看能不能符合预期,链式调用 then 吧。

输出结果为:

image-20230712142155920
image-20230712142155920

成功符合预期!

4. 异步事件

Promises/A+ 规范 要求 onFulfilled、onRejected 在执行上下文堆栈之前不得调用。也就是3.1.1标明要注意的点。

4.1 事件队列

当遇到一个异步事件后,并不会一直等待异步事件返回结果,而是会将这个事件挂在与执行栈不同的队列中,我们称之为事件队列。

当所有同步任务执行完成后,系统才会读取”事件队列”。

事件队列中的事件分为宏任务和微任务:

  1. 宏任务:浏览器/Node发起的任务,如 window.setTimeout;
  2. 微任务:Js 自身发起的,如 Promise;

事件队列就是先执行微任务,再执行宏任务,而宏任务和微任务包含以下事件:

宏任务

微任务

setTimeout

Promise

setInterval

queueMicrotask

script(整体代码块)

-

看看下面这个例子,你知道答案吗?

代码语言:javascript
复制
setTimeout(function () {
  console.log(1)
})
new Promise(function(resolve,reject){
  console.log(2)
  resolve(3)
}).then(function(val){
  console.log(val)
})
console.log(4)

打印结果的顺序是2->4->3->1。事件队列如下:

主队列,同步任务,new Promise 内部的同步任务

代码语言:javascript
复制
new Promise(function(resolve,reject){
  console.log(2)
})

主队列,同步任务,new Promise 后的 console.log(4)

代码语言:javascript
复制
console.log(4)

异步任务的微任务

代码语言:javascript
复制
promise.then(function(val){
  console.log(val)
})

异步任务的宏任务

代码语言:javascript
复制
setTimeout(function () {
  console.log(1)
})

因此,想要实现 onFulfilled、onRejected 在执行上下文堆栈之前不得调用,我们需要把 onFulfilled、onRejected 改造成微任务,这里使用 queueMicrotask 来模拟实现微任务,代码如下:

代码语言:javascript
复制
class MyPromise {
  then (onFulfilled, onRejected) {
    const realOnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
    const realOnRejected = typeof onRejected === 'function' ? onRejected : error => { throw error }

    const promise2 = new MyPromise((resolve, reject) => {

      const fulfilledMicrotask = () =>  {
        // 创建一个微任务等待 promise2 完成初始化
        queueMicrotask(() => {
          try {
            // 获取成功回调函数的执行结果
            const x = realOnFulfilled(this.value)
            // 传入 resolvePromise 集中处理
            resolvePromise(promise2, x, resolve, reject)
          } catch (error) {
            reject(error)
          } 
        })  
      }

      const rejectedMicrotask = () => { 
        // 创建一个微任务等待 promise2 完成初始化
        queueMicrotask(() => {
          try {
            // 调用失败回调,并且把原因返回
            const x = realOnRejected(this.reason)
            // 传入 resolvePromise 集中处理
            resolvePromise(promise2, x, resolve, reject)
          } catch (error) {
            reject(error)
          } 
        }) 
      }

      if (this.status === STATUS.PENDING) {
        this.onFulfilledCallbacks.push(fulfilledMicrotask)
        this.onRejectedCallbacks.push(rejectedMicrotask)
      } else if (this.status === STATUS.FULFILLED) {
        fulfilledMicrotask()
      } else if (this.status === STATUS.REJECTED) {
        rejectedMicrotask()
      }
    }) 

    return promise2
  }
}
4.2 试试看

打印结果如图:

image-20230712142636615
image-20230712142636615

成功按顺序打印。

3. Promise/A+ 测试

下面将用 Promise/A+ 测试工具 promises-aplus-tests 测试我们手写的 Promise 是否符合规范。

3.1 安装 promises-aplus-tests

代码语言:javascript
复制
npm install promises-aplus-tests -D

3.2 为 MyPromise 添加 deferred

代码语言:javascript
复制
MyPromise {
  ......
}

MyPromise.deferred = function () {
  var result = {};
  result.promise = new MyPromise(function (resolve, reject) {
    result.resolve = resolve
    result.reject = reject
  });

  return result;
}
module.exports = MyPromise

3.3 配置启动命令

代码语言:javascript
复制
"scripts": {
  "test:promise": "promises-aplus-tests ./Promise/index"
}

3.4 开始测试

代码语言:javascript
复制
npm run test:promise

哇哦,全部成功!!

image-20230712142844849
image-20230712142844849

3.5 完整代码

截止到此,完整代码如下:

代码语言:javascript
复制
const STATUS = {
  PENDING: 'pending',
  FULFILLED: 'fulfilled',
  REJECTED: 'rejected'
}

class MyPromise {

  constructor (executor) {
    // 执行器
    try {
      executor(this.resolve, this.reject)
    } catch (error) {
      this.reject(error)
    }
  }

  // 初始状态为 pending
  status = STATUS.PENDING

  // 成功返回值
  value = null

  // 失败返回值
  reason = null

  // 成功回调
  onFulfilledCallbacks = []

  // 失败回调
  onRejectedCallbacks = []

  // 修改 Promise 状态,并定义成功返回值
  resolve = value => {
    if (this.status === STATUS.PENDING) {
      this.status = STATUS.FULFILLED
      this.value = value
      // this.onFulfilledCallbacks.forEach(fn => fn())
      while(this.onFulfilledCallbacks.length) {
        this.onFulfilledCallbacks.shift()(value)
      }
    }
  }

  // 修改 Promise 状态,并定义失败返回值
  reject = reason => {
    if (this.status === STATUS.PENDING) {
      this.status = STATUS.REJECTED
      this.reason = reason
      // this.onRejectedCallbacks.forEach(fn => fn())
      while(this.onRejectedCallbacks.length) {
        this.onRejectedCallbacks.shift()(reason)
      }
    }
  }

  then = function (onFulfilled, onRejected) {
    // onFulfilled、onRejected 是可选参数 不是函数则必须忽略它
    const realOnFulfilled = onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
    const realOnRejected = onRejected = typeof onRejected === 'function' ? onRejected : error => { throw error }

    const promise2 = new MyPromise((resolve, reject) => {
      // 报错:Cannot access 'promise2' before initialization | 不能访问 promise2 在初始化之前
      // 原因:在 new promise 时,promise2 还没有完成初始化,所以 resolvePromise 中不能访问到 promise2
      // 在当前的执行上下文栈中,onFulfilled 或 onRejected 是不能被直接调用的
      // onFulfilled 或 onRejected 得是在当前事件循环后异步执行的
      // 可以使用 setTimeout、setImmediate、MutationObserever、process.nextTick在 then 方法被调用后将创建一个新的栈

      const fulfilledMicrotask = () => {
        // 创建一个微任务等待 promise2 完成初始化
        queueMicrotask(() => {
          try {
            // 获取成功回调函数的执行结果
            const x = realOnFulfilled(this.value)
            // 传入 resolvePromise 集中处理
            resolvePromise(promise2, x, resolve, reject)
          } catch (error) {
            reject(error)
          } 
        })
      }

      const rejectedMicrotask = () => {
        queueMicrotask(() => {
          try {
            // 获取成功回调函数的执行结果
            const x = realOnRejected(this.reason)
            // 传入 resolvePromise 集中处理
            resolvePromise(promise2, x, resolve, reject)
          } catch (error) {
            reject(error)
          }
        })
      }

      if (this.status === STATUS.PENDING) {
        this.onFulfilledCallbacks.push(fulfilledMicrotask)
        this.onRejectedCallbacks.push(rejectedMicrotask)
      } else if (this.status === STATUS.FULFILLED) {
        fulfilledMicrotask()
      } else if (this.status === STATUS.REJECTED) {
        rejectedMicrotask()
      }
    })

    return promise2
  }
}

function resolvePromise(promise2, x, resolve, reject) {
  // 如果 promise2 === x 执行 reject,错误原因为 TypeError
  if (promise2 === x) {
    reject(new TypeError('The promise and the return value are the same'))
  }

  // 如果 x 是函数或对象
  if ((typeof x === 'object' && x != null) || typeof x === 'function') {
    let then
    try {
      then = x.then
    } catch (error) {
      reject(error)
    }

    // 如果 x.then 是函数
    if (typeof then === 'function') {
      // Promise/A+ 2.3.3.3.3 只能调用一次
      let called = false
      try {
        then.call(x, y => {
          // resolve的结果依旧是promise 那就继续解析 | 递归解析
          if (called) return
          called = true
          resolvePromise(promise2, y, resolve, reject)
        }, err => {
          if (called) return
          called = true
          reject(err)
        })
      } catch (error) {
        if (called) return
        reject(error)
      }
    } else {
      // 如果 x.then 不是函数
      resolve(x)
    }
  } else {
    // 如果 x 是个普通值就直接返回 resolve 作为结果  Promise/A+ 2.3.4  
    resolve(x)
  }
}

// 测试 Promise 是否符合规范
MyPromise.deferred = function () {
  var result = {}
  result.promise = new MyPromise(function (resolve, reject) {
    result.resolve = resolve
    result.reject = reject
  })

  return result
}
module.exports = MyPromise

下面的完善Promise 的 API将基于此基础代码。

4. Promise 的 API

虽然上述的 promise 源码已经符合 Promise/A+ 的规范,但是原生的 Promise 还提供了一些其他方法,如:

  • Promise.resolve()
  • Promise.reject()
  • Promise.prototype.catch()
  • Promise.prototype.finally()
  • Promise.all()
  • Promise.race()

下面具体说一下每个方法的实现:

4.1 Promise.prototype.catch

Promise.prototype.catch 用来捕获 promise 的异常,就相当于一个没有成功的 then

至于为什么先实现此方法,是为了防止实现其他 api 时会报错。

代码语言:javascript
复制
// ...
catch(errCallback) {
  return this.then(null,errCallback)
}
// ...

4.2 Promise.prototype.finally

finally 表示不是最终的意思,而是无论如何都会执行的意思。 如果返回一个 promise 会等待这个 promise 也执行完毕。如果返回的是成功的 promise,会采用上一次的结果;如果返回的是失败的 promise,会用这个失败的结果,传到 catch 中。

代码语言:javascript
复制
finally(callback) {
  return this.then((value)=>{
    return MyPromise.resolve(callback()).then(()=>value)
  },(reason)=>{
    return MyPromise.resolve(callback()).then(()=>{throw reason})
  })  
}

测试一下

代码语言:javascript
复制
MyPromise.resolve(456).finally(()=>{
  return new MyPromise((resolve,reject)=>{
    setTimeout(() => {
        resolve(123)
    }, 3000)
  })
}).then(data=>{
  console.log(data,'success')
}).catch(err=>{
  console.log(err,'error')
})

控制台等待 3s 后输出:456 'success',相反如果把resolve(123)改为reject(123) 等待 3s后输出123 'error'

image-20230712151918462
image-20230712151918462

4.3 Promise.resolve

默认产生一个成功的 promise。

代码语言:javascript
复制
static resolve(data){
  return new MyPromise((resolve,reject)=>{
    resolve(data)
  })
}

这里需要注意的是,promise.resolve 是具备等待功能的。如果参数是 promise 会等待这个 promise 解析完毕,在向下执行,所以这里需要在原来 resolve 方法中做一个小小的处理:

代码语言:javascript
复制
// 修改 Promise 状态,并定义成功返回值
resolve = value => {
  if(value instanceof MyPromise){
    // 递归解析 
    return value.then(this.resolve,this.reject)
  }
  if (this.status === STATUS.PENDING) {
    this.status = STATUS.FULFILLED
    this.value = value
    while(this.onFulfilledCallbacks.length) {
      this.onFulfilledCallbacks.shift()(value)
    }
  }
}

4.4 Promise.reject

代码语言:javascript
复制
static reject(reason){
  return new MyPromise((resolve,reject)=>{
    reject(reason)
  })
}

4.5 Promise.all

Promise.all 是解决并发问题的,多个异步并发获取最终的结果(如果有一个失败则失败)。

Promise.all方法可以接收一个promise数组作为参数,返回一个新的promise对象,该对象在数组中所有promise都成功时才会被resolve。如果其中有一个promise失败,则Promise.all会立即将其reject,并且不再等待其他promise的执行结果。

注意:这个参数数组里面也不是必须都是promise,也可以是常量普通值。

代码语言:javascript
复制
static all(values) {
  if (!Array.isArray(values)) {
    const type = typeof values
    return new TypeError(`TypeError: ${type} ${values} is not iterable`)
  }

  return new MyPromise((resolve, reject) => {
    let resultArr = []
    let orderIndex = 0
    const processResultByKey = (value, index) => {
      resultArr[index] = value
      if (++orderIndex === values.length) {
        resolve(resultArr)
      }
    }
    for (let i = 0; i < values.length; i++) {
      let value = values[i]
      if (value && typeof value.then === 'function') {
        value.then((value) => {
          processResultByKey(value, i)
        }, reject)
      } else {
        processResultByKey(value, i)
      }
    }
  })
}

测试一下:

代码语言:javascript
复制
let p1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('ok1')
  }, 1000)
})

let p2 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('ok2')
  }, 1000)
})

MyPromise.all([1,2,3,p1,p2]).then(data => {
  console.log('resolve', data)
}, err => {
  console.log('reject', err)
})

控制台等待 1s 后输出:resolve (5) [1, 2, 3, 'ok1', 'ok2']

4.6 Promise.race

Promise.race 用来处理多个请求,采用最快的(谁先完成用谁的)。

代码语言:javascript
复制
static race(promises) {
  return new MyPromise((resolve, reject) => {
    // 一起执行就是for循环
    for (let i = 0; i < promises.length; i++) {
      let val = promises[i]
      if (val && typeof val.then === 'function') {
        val.then(resolve, reject)
      } else { // 普通值
        resolve(val)
      }
    }
  })
}

特别需要注意的是:因为Promise 是没有中断方法的,xhr.abort()、ajax 有自己的中断方法,axios 是基于 ajax 实现的;fetch 基于 promise,所以他的请求是无法中断的。

这也是 promise 存在的缺陷,我们可以使用 race 来自己封装中断方法:

代码语言:javascript
复制
function wrap(promise) {
  // 在这里包装一个 promise,可以控制原来的promise是成功还是失败
  let abort
  let newPromise = new MyPromise((resolve, reject) => { // defer 方法
      abort = reject
  });
  let p = MyPromise.race([promise, newPromise]) // 任何一个先成功或者失败 就可以获取到结果
  p.abort = abort
  return p
}

const promise = new MyPromise((resolve, reject) => {
  setTimeout(() => { // 模拟的接口调用 ajax 肯定有超时设置
    resolve('成功')
  }, 1000);
});

let newPromise = wrap(promise)

setTimeout(() => {
  // 超过3秒 就算超时 应该让 proimise 走到失败态
  newPromise.abort('超时了')
}, 3000)

newPromise.then((data => {
  console.log('成功的结果' + data)
})).catch(e => {
  console.log('失败的结果' + e)
})

控制台等待 1s 后输出:成功的结果成功

5.总结

以上,我们实现了一个符合 Promises/A+ 规范 的 Promise,也实现了Promise一些常用API方法。

总结一下 Promise 其实就是一个帮助我们执行异步任务的对象,因为 Javascript 单线程的特性,导致必须通过为异步任务添加回调来得到异步任务的结果。为了解决回调地狱,Promise 应运而生。

Promise 通过对异步任务执行状态的处理,让我们可以在 Promise.then 中获取任务结果,让代码更加清晰优雅。

Promise.then 的链式调用,以顺序的方式来表达异步流,让我们更好的维护异步代码。

注:该文基于 https://juejin.cn/post/6973155726302642206 基础上转载修改完善,感谢。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-7-28 1,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、前言
    • 1.1 小例子
    • 二、Promises/A+
      • 1. Promise 基本用法
        • 1.1 Promise 状态
        • 1.2 执行器
        • 1.3 then
        • 1.4 试试看
      • 2. Promise.then
        • 1. 可选参数
        • 2. 多次调用 then
        • 3. 链式调用 then
        • 4. 异步事件
      • 3. Promise/A+ 测试
        • 3.1 安装 promises-aplus-tests
        • 3.2 为 MyPromise 添加 deferred
        • 3.3 配置启动命令
        • 3.4 开始测试
        • 3.5 完整代码
      • 4. Promise 的 API
        • 4.1 Promise.prototype.catch
        • 4.2 Promise.prototype.finally
        • 4.3 Promise.resolve
        • 4.4 Promise.reject
        • 4.5 Promise.all
        • 4.6 Promise.race
      • 5.总结
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档