Skip to content

Promise

如何玩转 ES6 的 Promise

一、Promise是什么

1.1 理解

  • Promise 是一门新的技术(ES6 规范),是 JS 中进行异步编程的新解决方案(旧方案是单纯使用回调函数,容易形成回调地狱)
  • 常见异步操作:①fs 文件操作 ②数据库操作 ③Ajax ④定时器
  • 具体表达:
    1. 从语法上看:Promise是一个构造函数 (自己身上有all、reject、resolve这几个方法,原型上有then、catch等方法)
    2. 从功能上看:promise对象用来封装一个异步操作并可以获取其成功/失败的结果值

1.2 Promise的状态改变

实例对象promise中的一个属性 PromiseState,表示promise的状态

  • pending 变为 resolved/fullfilled 成功
  • pending 变为 rejected 失败

注意点:

  1. 对象的状态不受外界影响
  2. 状态只有这两种,且一个 promise 对象只能改变一次
  3. 一旦状态改变,就不会再变,任何时候都可以得到这个结果
  4. 无论成功还是失败,都会有一个结果数据。成功的结果数据一般称为 value,而失败的一般称为 reason

1.3 Promise 的基本流程

img

1.4 Promise 的基本使用

javascript
const promise = new Promise(function(resolve, reject) {
  // ... some code
  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(reason);
  }
});

1、Promise构造函数接受一个函数(执行器函数)作为参数,该函数的两个参数分别是resolve和reject它们是两个函数,由 JavaScript 引擎提供,不用自己部署

2、resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”,在异步操作成功时调用,并将异步操作的结果,作为参数value传递出去;

3、reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”,在异步操作失败时调用,并将异步操作报出的错误,作为参数error/reason传递出去。

4、Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数

javascript
promise.then(function(value) {
  // success
}, function(reason) {
  // failure
});

then方法可以接受两个回调函数作为参数。

第一个回调函数onResolved()是Promise对象的状态变为resolved时调用

第二个回调函数onRejected()是Promise对象的状态变为rejected时调用

这两个函数都是可选的,不一定要提供。它们都接受Promise对象传出的值作为参数

二、为什么要用 Promise

的方式更加灵活

  • 旧语法:必须在启动异步任务前指定
javascript
// 1. 纯回调的形式
// 成功的回调函数
function successCallback(result) {
  console.log("声音文件创建成功:" + result);
}
// 失败的回调函数
function failureCallback(error) {
  console.log("声音文件创建失败:" + error);
}
// 必须先指定回调函数,再执行异步任务
createAudioFileAsync(audioSettings, successCallback, failureCallback) // 回调函数在执行异步任务(函数)前就要指定
  • Promise: 启动异步任务 => 返回Promise对象 => 给Promise对象绑定回调函数(甚至可以任务结束后指定多个回调)
javascript
// 2. 使用Promise
const promise = createAudioFileAsync(audioSettings);  // 执行2秒
setTimeout(() => {
  promise.then(successCallback, failureCallback) // 也可以获取
}, 3000);

2.2 支持链式调用, 可以解决回调地狱问题

什么是回调地狱?

  • 回调函数嵌套调用, 外部回调函数异步执行的结果是嵌套的回调执行的条件
javascript
doSomething(function(result) {
  doSomethingElse(result, function(newResult) {
    doThirdThing(newResult, function(finalResult) {
      console.log('Got the final result:' + finalResult)
    }, failureCallback)
  }, failureCallback)
}, failureCallback)

回调地狱的缺点?

  • 不便于阅读
  • 不便于异常处理

解终极解决方案?

  • Promise链式调用
javascript
doSomething()
  .then(result => doSomethingElse(result))
  .then(newResult => doThirdThing(newResult))
  .then(finalResult => {console.log('Got the final result:' + finalResult)})
  .catch(failureCallback)

三、如何使用 Promise

3.1 Promise 构造函数:Promise(executor) {}

  • executor 函数:同步执行 (resolve, reject) => {}
  • resolve 函数:内部定义成功时调用的函数 resove(value)
  • reject 函数:内部定义失败时调用的函数 reject(reason)

executor 是执行器,会在 Promise 内部立即同步回调,异步操作 resolve/reject 就在 executor 中执行

3.2 Promise.prototype.then 方法:p.then(onResolved, onRejected)

指定两个回调(成功+失败)

  • onResolved 函数:成功的回调函数 (value) => {}
  • onRejected 函数:失败的回调函数 (reason) => {}

指定用于得到成功 value 的成功回调和用于得到失败 reason 的失败回调,返回一个新的 promise 对象

3.3 Promise.prototype.catch 方法:p.catch(onRejected)

指定失败的回调

  • onRejected 函数:失败的回调函数 (reason) => {}

说明:这是then() 的语法糖,相当于 then(undefined, onRejected)

3.4 Promise.resolve 方法:Promise.resolve(value)

value:将被 Promise 对象解析的参数,也可以是一个成功或失败的 Promise 对象

返回一个带着给定值解析过的 Promise 对象,如果参数本身就是一个 Promise 对象,则直接返回这个 Promise 对象

  1. 如果传入的参数为 非Promise类型的对象, 则返回的结果为成功promise对象
javascript
let p1 = Promise.resolve(521);
console.log(p1); // Promise {<fulfilled>: 521}
  1. 如果传入的参数为 Promise 对象, 则参数的结果决定了 resolve 的结果
javascript
let p2 = Promise.resolve(new Promise((resolve, reject) => {
    // resolve('OK'); // 成功的Promise
    reject('Error');
}));
console.log(p2);
p2.catch(reason => {
    console.log(reason);
})

3.5 Promise.reject 方法:Promise.resolve(reason)

reason:失败的原因

说明:返回一个失败的 promise 对象

javascript
let p = Promise.reject(521);
let p2 = Promise.reject('iloveyou');
let p3 = Promise.reject(new Promise((resolve, reject) => {
    resolve('OK');
}));

console.log(p);
console.log(p2);
console.log(p3);   //reject 就算返回的promise调用的是resolve
  • Promise.resolve()/Promise.reject() 方法就是一个语法糖,用来快速得到Promise对象

3.6 Promise.all 方法:Promise.all(iterable)

iterable:包含 n 个 promise 的可迭代对象,如 Array 或 String

说明:返回一个新的 promise,只有所有的 promise 都成功才成功,只要有一个失败了就直接失败

javascript
let p1 = new Promise((resolve, reject) => {
  resolve('OK');
})
let p2 = Promise.resolve('Success');
let p3 = Promise.resolve('Oh Yeah');

const result = Promise.all([p1, p2, p3]);
console.log(result);    //全部成功才算成功

img

javascript
let p1 = new Promise((resolve, reject) => {
  resolve('OK');
})
let p2 = Promise.reject('Error');
let p3 = Promise.resolve('Oh Yeah');

const result = Promise.all([p1, p2, p3]);
console.log(result);   //只要有一个失败了停止了,并返回报错信息

img

3.7 Promise.race方法:Promise.race(iterable)

iterable:包含 n 个 promise 的可迭代对象,如 Array 或 String

说明:返回一个新的 promise,第一个完成的 promise 的结果状态就是最终的结果状态

谁先完成就输出谁(不管是成功还是失败)

javascript
let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
      resolve('OK');
  }, 1000);
})
let p2 = Promise.resolve('Success');
let p3 = Promise.resolve('Oh Yeah');

//调用
const result = Promise.race([p1, p2, p3]);

console.log(result);

img

3.8 Promise.allSettled方法:Promise.allSettled(iterable)

iterable:包含 n 个 promise 的可迭代对象,如 Array 或 String

说明:返回一个新的 promise,并且返回的状态始终是成功的,返回的结果包含每一个promise对象的状态和返回值

javascript
let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
      resolve('OK');
  }, 1000);
})
let p2 = Promise.reject('Success');
let p3 = Promise.resolve('Oh Yeah');
//调用
const result = Promise.allSettled([p1, p2, p3]);
console.log(result);

img

四、Promise的几个关键问题

4.1 如何改变 promise 的状态?

  1. resolve(value):如果当前是 pending 就会变为 resolved
  2. reject(reason):如果当前是 pending 就会变为 rejected
  3. 抛出异常:如果当前是 pending 就会变为 rejected

4.2 一个 promise 指定多个成功/失败回调函数,都会调用吗?

当 promise 改变为对应状态时都会调用

javascript
const p = new Promise((resolve, reject) => {
  //resolve(1)
  reject(2)
})
p.then(
  value => {},
  reason => {console.log('reason',reason)}
)
p.then(
  value => {},
  reason => {console.log('reason2',reason)}
)
// reason 2
// reason2 2

4.3 promise.then() 返回的新 promise 的结果状态由什么决定?

(1)简单表达:由 then() 指定的回调函数执行的结果决定

(2)详细表达:

① 如果抛出异常,新 promise 变为 rejected,reason 为抛出的异常

② 如果返回的是非 promise 的任意值,新 promise 变为 resolved,value 为返回的值

③ 如果返回的是另一个新 promise,此 promise 的结果就会成为新 promise 的结果

4.4 promise 如何串联多个操作任务?

(1)promise 的 then() 返回一个新的 promise,可以并成 then() 的链式调用

(2)通过 then 的链式调用串联多个同步/异步任务

javascript
new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('执行任务1(异步)')
    resolve(1)
  }, 1000)
}).then(
  value => {
    console.log('任务1的结果', value)
    console.log('执行任务2(同步)')
    return 2 // 同步任务直接return返回结果
  }
).then(
  value => {
    console.log('任务2的结果', value)
    return new Promise((resolve, reject) => { // 异步任务需要包裹在Promise对象中
      setTimeout(() => {
        console.log('执行任务3(异步)')
        resolve(3)
      }, 1000)
    })
  }
).then(
  value => {
    console.log('任务3的结果', value)
  }
)
// 执行任务1(异步)
// 任务1的结果 1
// 执行任务2(同步)
// 任务2的结果 2
// 执行任务3(异步)
// 任务3的结果 3

4.5 Promise 异常穿透(传透)?

(1)当使用 promise 的 then 链式调用时,可以在最后指定失败的回调

(2)前面任何操作出了异常,都会传到最后失败的回调中处理

javascript
new Promise((resolve, reject) => {
   //resolve(1)
   reject(1)
}).then(
  value => {
    console.log('onResolved1()', value)
    return 2
  }
).then(
  value => {
    console.log('onResolved2()', value)
    return 3
  }
).then(
  value => {
    console.log('onResolved3()', value)
  }
).catch(
  reason => {
    console.log('onRejected1()', reason)
  }
)
// onRejected1() 1

失败的结果是一层一层处理下来的,最后传递到 catch 中

4.6 中断 promise 链?

当使用 promise 的 then 链式调用时,在中间中断,不再调用后面的回调函数

办法:在回调函数中返回一个 pending 状态的 promise 对象(插入一个空promise)

javascript
new Promise((resolve, reject) => {
   //resolve(1)
   reject(1)
}).then(
  value => {
    console.log('onResolved1()', value)
    return 2
  }
).then(
  value => {
    console.log('onResolved2()', value)
    return 3
  }
).then(
  value => {
    console.log('onResolved3()', value)
  }
).catch(
  reason => {
    console.log('onRejected1()', reason)
    return new Promise(() => {}) // 返回一个pending的promise
  }
).then(
  value => {
    console.log('onResolved4()', value)
  },
  reason => {
    console.log('onRejected2()', reason)
  }
)
// onRejected1() 1

分析:

在 catch 中返回一个新的 promise,且这个 promise 没有结果。

由于,返回的新的 promise 结果决定了后面 then 中的结果,所以后面的 then 中也没有结果。

这就实现了中断 promise链的效果。

五、手写一个Promise

javascript
/**
 * 自定义Promise函数模块:IIFE
 */
(function (window) {

  const PENDING = 'pending'
  const RESOLVED = 'fulfilled'
  const REJECTED = 'rejected'

  class Promise {
    /**
     * Promise构造函数
     * @param {function} executor 执行器函数(同步执行)(resolve, reject) => {}
     */
    constructor(executor) {

      const self = this; // 保存当前实例对象的this的值
      // 添加属性
      self.PromiseState = PENDING // 给promise对象指定status属性,初始值为pending
      self.PromiseResult = null // 给promise对象指定一个用于存储结果数据的属性
      self.callbacks = [] // 存的是对象 每个元素的结构:{onResolved() {}, onRejected() {}}

      /**
       * executor内部定义成功时调用的函数
       * @param {*} value 成功的值
       * @returns 
       */
      function resolve(value) {
        // 如果当前状态不是pending,直接结束
        if (self.PromiseState !== PENDING) return
        // 1. 修改对象的状态(promiseState)为 fulfilled
        self.PromiseState = RESOLVED
        // 2. 设置对象结果值(promiseResult)为 value
        self.PromiseResult = value
        // 如果有待执行的callback函数,立即【异步】执行回调函数onResolved
        if (self.callbacks.length > 0) {
          setTimeout(() => { // 放入队列中执行所有成功的回调
            self.callbacks.forEach(callbacksObj => {
              callbacksObj.onResolved(value)
            })
          }, 0)
        }
      }
    
      /**
       * executor内部定义失败时调用的函数
       * @param {*} reason 失败的原因
       * @returns 
       */
      function reject(reason) {
        // 如果当前状态不是pending,直接结束
        if (self.PromiseState !== PENDING) return
        // 1. 修改对象的状态(promiseState)为 rejected
        self.PromiseState = REJECTED
        // 2. 设置对象结果值(promiseResult)为 reason
        self.PromiseResult = reason
        // 如果有待执行的callback函数,立即【异步】执行回调函数onRejected
        if (self.callbacks.length > 0) {
          setTimeout(() => { // 放入队列中执行所有失败的回调
            self.callbacks.forEach(callbacksObj => {
              callbacksObj.onRejected(reason)
            })
          }, 0)
        }
      }
    
      // 立即【同步】执行executor函数
      try {
        executor(resolve, reject)
      } catch (error) { // 如果执行器抛出异常,promise对象变成rejected状态
        reject(error)
      }
    }

    /**
     * Promise原型对象then方法 
     * 指定成功和失败的回调函数
     * @param {function} onResolved 成功的回调函数(value) => {}
     * @param {function} onRejected 失败的回调函数(reason) => {}
     * @returns 一个新的promise对象结果由onResolved/onRejected执行的结果决定
     */
    then (onResolved, onRejected) {
      // 指定默认的成功的回调onResolved (向后传递成功的value)
      onResolved = typeof onResolved === 'function' ? onResolved : value => value
      // 指定默认的失败的回调onRejected(向后传递失败的reason 实现错误/异常传透的关键点)
      onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason}

      const self = this

      return new Promise((resolve, reject) => {
        /**
         * 调用指定回调函数处理,根据执行的结果改变return的promise的状态
         * @param {function} callback 指定回调函数
         */
        function handle(callback) {
          try {
            const result = callback(self.PromiseResult) // result获取回调函数执行(return)的结果
            if (result instanceof Promise) { // 3. 如果回调函数返回的是promise
              result.then(resolve, reject) // 简洁写法
            } else { // 2. 如果回调函数返回的不是promise
              resolve(result)
            }
          } catch (error) { //1. 如果抛出异常
            reject(error)
          }
        }

        if (self.PromiseState === PENDING) { // 1. 当前状态是pending状态,将回调函数保存起来
          self.callbacks.push({
            onResolved(value) { //执行成功的回调函数,改promise的状态
              handle(onResolved)
            },
            onRejected(reason) { //执行失败的回调函数,改promise的状态
              handle(onRejected)
            }
          })
        } else if (self.PromiseState === RESOLVED) { // 2. resolved,【异步】执行onResolved并改变return的promise的状态
          setTimeout(() => {
            handle(onResolved)
          }, 0)
        } else { // 3. rejected,【异步】执行onRejected并改变return的promise的状态
          setTimeout(() => {
            handle(onRejected)
          }, 0)
        }
      })
    }


    /**
     * Promise原型对象catch方法
     * 指定失败的回调函数
     * @param {function} onRejected 失败的回调函数(reason) => {}
     * @returns 一个新的promise对象
     */
    catch (onRejected) {
      return this.then(undefined, onRejected)
    }


    /**
     * Promise函数对象resolve方法
     * @param {*} value 成功的值
     * @returns 一个成功/失败的promise
     */
    static resolve(value) {
      // 返回一个成功/失败的promise
      return new Promise((resolve, reject) => {
        if (value instanceof Promise) { // value是promise => 使用value的结果作为promise的结果
          value.then(resolve,reject)
        } else { // value不是promise => promise状态变为成功,数据是value
          resolve(value)
        }
      })
    }


    /**
     * Promise函数对象reject方法
     * @param {*} resaon 失败的原因
     * @returns 一个失败的promise
     */
    static reject (reason) {
      // 返回一个失败的promise
      return new Promise((resolve, reject) => {
        reject(reason)
      })
    }

  
    /**
     * Promise函数对象all方法
     * @param {Array<Promise>} promises 
     * @returns 一个promise,只有当所有promise都成功时才成功,否则只要有一个失败就失败
     */
     static all (promises) {
      return new Promise((resolve, reject) => {
        let count = 0 // 声明计数变量
        // const values = []; // 保存每个成功promise结果的数组
        const values = new Array(promises.length) // 指定数组长度
        for (let i = 0; i < promises.length; i++){
          // promises[i].then(value => {
            Promise.resolve(promises[i]).then(value => { // 防止数组中有不是promise的元素
            // 得知对象状态是成功
            count++
            // 将当前promise对象成功的结果存入到数组中
            values[i] = value
            if (count === promises.length) { //每个promise对象都成功
              resolve(values) // 修改函数状态
            }
          }, reason => {
              reject(reason)
            })
        }
      })
    }


    /**
     *Promise函数对象race方法
    * @param {Array<Promise>} promises 
    * @returns 返回 一个promise,其结果由第一个完成的promise决定
    */
    static race (promises) {
      return new Promise((resolve, reject) => {
        for (let i = 0; i < promises.length; i++){
          // promises[i].then(value => {
          Promise.resolve(promises[i]).then(value => { // 防止数组中有不是promise的元素
            // 修改返回对象的状态为 成功
            resolve(value)
          }, reason => {
            reject(reason)
          })
        }
      })
    }

    /**
     * 自定义方法 延时返回一个成功/失败的promise
     * @param {*} value 成功的数据
     * @param {Number} timeout 延迟时间
     * @returns 一个成功/失败的promise
     */
    static resolveDelay (value, timeout) {
      // 延时返回一个成功/失败的promise
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          if (value instanceof Promise) { // value是promise => 使用value的结果作为promise的结果
            value.then(resolve,reject)
          } else { // value不是promise => promise状态变为成功,数据是value
            resolve(value)
          }
        })
      }, timeout);
    }

    /**
     * 自定义方法 延时返回一个失败的promise
     * @param {*} reason 失败的原因
     * @param {*} timeout 延迟时间
     * @returns 一个失败的promise
     */
     static rejectDelay (reason, timeout) {
      // 延时返回一个失败的promise
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          reject(reason)
        }, timeout)
      })
    }
  }

  // 向外暴露Promise函数
  window.Promise = Promise
})(window)