深入浅出FE(一)promise对象

226 阅读9分钟

一、定义

ECMA-262(ES6)给出的定义是:

A Promise is an object that is used as a placeholder for the eventual results of a deferred (and possibly asynchronous) computation.

翻译成中文就是:Promise对象是用来作为延迟(包含异步)运算的最终结果的占位符。

标准给出的定义是抽象的,我给出的定义是:

Promise对象是一个异步操作的容器,是一个包含对未来事件的结果(可以是异步的),是一个穿越时间循环存在的对象。Promise有三种状态:Pending(进行中)、Resolved(已完成,又称Fulfilled)和Rejected(已失败)。并且Promise是异步编程的解决方案,或者说主要是用同步的方式来书写异步。

基本的概念本篇教程不再复述,本篇不是入门教程,如果你此前不了解Promise,请先自行了解。

二、关于Promise

1.首先Promise是一个构造函数,可以生成Promise实例。

var promise = new Promise(function(resolve, reject) {
  // ... some code

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

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolvereject。它们是两个函数。resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从Pending变为Resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从Pending变为Rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

2.Promise.prototype.then()和Promise.prototype.catch()

(1)resolved状态的Promise会回调后面的第一个then;

(2)rejected状态的promise会回调后面的第一个catch。

(3) then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。

/**
* 用setTimeout模拟异步操作
*/
const promise = new Promise((resolve, reject) => {
    setTimeout(function () {
        resolve(1);
    }, 500)
})

promise
    .then(function (result) {
        console.log(result)
    })
    .catch(function (err) {

    })

以上代码先返回一个pending状态的Promise,500ms后输出1,这也是一个事件循环,稍后会介绍。

3.任何一个rejected状态且后面没有catch的Promise都会造成浏览器(或者node)环境的全局错误,执行then和catch会返回一个新的Promise,该Promise最终状态根据then和catch回调函数的执行结果决定。

(1)若回调函数的最终结果是throw,则该promise是rejected状态;

(2)若回调函数的最终结果是return,则该Promise是resolved状态;

(3)若回调函数最终return了一个Promise,该Promise会和回调函数return的Promise的状态保持一致。

4.如果是多次异步调用,需要多个then,但是只需要一个catch。

任何一个then中返回的promise对象rejected都会被catch捕获。

/**
 * promise的链式调用
 */

function getResult(round) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (Math.random() < 0.2) {
                const error = new Error('failed');
                error.round = round;
                reject(error);

            } else {
                resolve('success');
            }
        }, 500)
    })
}

getResult(1)
    .then(()=> {
        return getResult(2);
    })
    .then(()=> {
        return getResult(3);
    })
    .then(()=> {
        if (Math.random() > 0.1) {
            const error = new Error('keyboard')
            error.round = 'keyboard'
            throw error 
        }
    })
    .catch((err)=> {
        console.log('cry at ' + err.round)
    })

5.并发异步Promise.all,第一个catch只能捕获第一个失败的结果。

Promise.all([
    getDecide('father').catch(() => { }),
    getDecide('mother'),
    getDecide('wife'),
    
]).then(() => {
    console.log('family all agree')

}).catch((err) => {
    console.log(err.name + ' not agree');
})
/**
*获取是否同意结果
*/
function getDecide(name) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (Math.random() < 0.2) {
                const error = new Error('disagree');
                error.name = name;
                reject(error);

            } else {
                resolve('agree');
            }
        }, Math.random() * 400)
    })
}

6.Promise.prototype.finally

不管成功还是失败,都会走到finally中,并且finally之后,还可以继续then。并且会将值原封不动的传递给后面的then.

Promise.prototype.finally = function (callback) {
    return this.then((value) => {
        return Promise.resolve(callback()).then(() => {
            return value;
        });
    }, (err) => {
        return Promise.resolve(callback()).then(() => {
            throw err;
        });
    });
}

7.Promise.race

当参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄,并返回该promise对象。

Promise.race函数返回一个 Promise,它将与第一个传递的 promise 相同的完成方式被完成。它可以是完成( resolveed),也可以是失败(rejected),这要取决于第一个完成的方式是两个中的哪个。

如果传的参数数组是空,则返回的 promise 将永远等待。

如果迭代包含一个或多个非承诺值和/或已解决/拒绝的承诺,则 Promise.race 将解析为迭代中找到的第一个值。

Promise.race = function (promises) {
    return new Promise((resolve, reject) => {
        if (promises.length === 0) {
            return;
        } else {
            for (let i = 0; i < promises.length; i++) {
                Promise.resolve(promises[i]).then((data) => {
                    resolve(data);
                    return;
                }, (err) => {
                    reject(err);
                    return;
                });
            }
        }
    });
}

8.async/await的执行结果是一个Promise,是Promise的语法糖。async/await是异步编程的终极解决方案,以同步的方式写异步。

(1)使用try/catch可以捕获await的错误

(2)await可以以同步的写法获取Promise的执行结果

(3)await可以“暂停”async function的执行

(4)如果要并行异步,await Promise.all([异步1,异步2,...])

三、事件循环中的Promise

事件循环指的是计算机系统的一种运行机制。JavaScript语言就采用这种机制,来解决单线程运行带来的一些问题。"Event Loop是一个程序结构,用于等待和发送消息和事件。是在程序中设置两个线程:一个负责程序本身的运行,称为"主线程";另一个负责主线程与其他进程(主要是各种I/O操作)的通信,被称为"Event Loop线程"(可以译为"消息线程")。

异步执行的运行机制如下。(同步执行也是如此,因为它可以被视为没有异步任务的异步执行。)

(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。

(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。

(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。

(4)主线程不断重复上面的第三步。

主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。

事件循环中的异步任务分为 宏任务(macrotask) 与 微任务 (microtask),不同的API注册的任务会依次进入自身对应的队列中,然后等待 Event Loop 将它们依次压入执行栈中执行。

宏任务(macrotask):

script(整体代码)、setTimeout、setInterval、UI 渲染、 I/O、postMessage、 MessageChannel、setImmediate(Node.js 环境)

微任务(microtask):

Promise、 MutaionObserver、process.nextTick(Node.js环境)

Event Loop(事件循环)中,每一次循环称为 tick, 每一次tick的任务如下:

  • 执行栈选择最先进入队列的宏任务(通常是script整体代码),如果有则执行
  • 检查是否存在 Microtask,如果存在则不停的执行,直至清空 microtask 队列
  • 更新render(每一次事件循环,浏览器都可能会去更新渲染)
  • 重复以上步骤

Promise属于微任务,所以会在执行完宏任务后执行。本文提到的一个例子:

const promise = new Promise((resolve, reject) => {
    setTimeout(function () {
        resolve(1);
    }, 500)
})

promise
    .then(function (result) {
        console.log(result)
    })
    .catch(function (err) {

    })

上面代码的执行结果是:先返回一个pending状态的Promise,然后输出1。



四、Promise A+规范的实现

根据promise A+规范实现promise,promise A+规范

// 三种状态
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";
// promise 接收一个函数参数,该函数会立即执行
function Promise(fn) {
  let _this = this;
  _this.currentState = PENDING;
  _this.value = undefined;
  // 用于保存 then 中的回调,只有当 promise
  // 状态为 pending 时才会缓存,并且每个实例至多缓存一个 
  _this.resolvedCallbacks = []; 
  _this.rejectedCallbacks = [];
  _this.resolve = function(value) {
    if (value instanceof Promise) {
      // 如果 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);
  }
}
Promise.prototype.then = function(onResolved, onRejected) {
  let self = this;
  // 规范 2.2.7,then 必须返回一个新的 promise
  let 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 : reason => { throw reason };
  if (self.currentState === RESOLVED) {
    return (promise2 = new Promise(function(resolve, reject) {
      // 规范 2.2.4,保证 onFulfilled,onRjected 异步执行
      // 所以用了 setTimeout 包裹下
      setTimeout(function() {
        try {
          let x = onResolved(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason);
        }
      });
    }));
  }
  if (self.currentState === REJECTED) {
    return (promise2 = new Promise(function(resolve, reject) {
      setTimeout(function() {
        // 异步执行onRejected
        try {
          let x = onRejected(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason);
        }
      });
    }));
  }
  if (self.currentState === PENDING) {
    return (promise2 = new Promise(function(resolve, reject) {
      self.resolvedCallbacks.push(function() {
        // 考虑到可能会有报错,所以使用 try/catch 包裹
        try {
          let x = onResolved(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (r) {
          reject(r);
        }
      });
      self.rejectedCallbacks.push(function() {
        try {
          let 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 Promise) {
    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);
  }
}



module.exports = Promise;

如果要对上面的promise实现测试,可以添加如下代码:

Promise.defer = Promise.deferred = function () {
    let dfd = {};
    dfd.promise = new Promise((resolve, reject) => {
        dfd.resolve = resolve;
        dfd.reject = reject;
    });
    return dfd;
}

安装promises-aplus-tests并执行测试

npm install -g promises-aplus-tests
promises-aplus-tests promise.js

结果截图,即通过了872个测试用例:

五、参考资料

1.ECMAScript® 2015 Language Specification

2.Promise 对象,「阮一峰官方博客」

3.Promise,「廖雪峰官方博客」

4.juejin.cn/user/336855…

5.Promise A+ 规范

6.promise,「MDN」