进一步理解promise

116 阅读4分钟

一 、浏览器执行异步代码的过程

一段js脚本通常由多个块组成,最常见的块单位是函数。对于js脚本,浏览器会调用js引擎进行解析和执行。如果在解析过程中遇到异步代码,js引擎会通知浏览器,等这段异步代码执行完成时,调用回调。浏览器就会监听异步事件,一旦获取到结果,就会把回调放入事件循环中,等待执行。

所以,js引擎不是独立运行的,也不是按照代码的书写顺序一行一行执行的,而是听从宿主环境(大多数情况下是浏览器)的调度,按需执行。

二、回调简介

最常见的处理异步结果的方式就是回调,回调的方式使js引擎不必等待异步代码的执行,从而减少代码阻塞,带来更好的交互体验。但回调并不符合我们的思考顺序,链式回调也会使代码难以理解,并可能会带来隐藏的bug

  1. 对于异步代码回调的执行,依赖于浏览器调用js引擎的时间,而不是代码顺序,比如下面这个例子
var x,y,z;
function f1(data) {
  x = data;
}
function f2(data) {
  y = data;
}
function f3() {
  return x + y;
}
var x = axios.get('xx.com', f1)
var y = axios.get('bb.com', f2) 
z = x + y

// 理想的结果是,执行到z = x + y时,已经获取到了x和y的值,但由于回调,此时的值还是不确定的
  1. 回调会导致代码不易理解

这里的不易理解,不在于回调嵌套层级,而在于异步和同步代码可能同时存在

doA(function(){
  doC();
  
  doD(function(){
    doF()
  })
  doE()
})
doB()

// 这里的执行顺序时A B C D E F,但是如果doA()或者doD()不是异步,那结果就是A C D F E B

  1. 回调引发的信任问题

我们使用的回调大多数是第三方提供的,对于回调什么时候调用,调用多少次,对我们来说是不确定的,并且,大多数的回调没有内置的错误处理机制,需要我们手动添加和处理

三 、promise简介

promise作为js内置的一种异步处理方案,比较好的解决了回调带来的问题

  1. 代码的执行顺序

对于promie来说,代码的执行具有归一性,顺序性

  new Promise((resolve, reject) => {
    ////
    console.log('begin');
    resolve(x)
  }).then((x) => {
    console.log(x) // 当我们使用x时,x的值已经是确定的,对比回调的第一个例子,在z = x + y中,x和y的值还不确定
    return new Promise((resolve, reject) => {
        setTimeout(() => {
          console.log('then1');
          resolve()
        })
    })
  }).then(() => {
    console.log('then2')
  })
  
  只要在then中返回一个promise或者一个值,那么then一定是按顺序调用,并且是异步调用的
  1. 信任问题

对于同一个promise,即时调用resolve或reject多次,它上面的then只会执行一次,并且一定是异步执行。promise还提供了错误处理机制catch

虽然promise解决了回调带来的问题,但它本身也有一定的局限性

  1. promise无法终止,并且如果catch中抛错,promise也无法捕获到
function textError() {
  try {
   new Promise((resolve, reject) => reject(1));
    p1.catch(e => foo()) // foo不存在,会抛错,但不会被catch捕获到,因为try catch只能捕获同步错误
  }catch(e) {
    console.log('a')
  }

console.log('dfs')
}
  1. promise的单决议,即promise的值只会获取一次
var p = new Promise((resolve, reject) => {
  window.addEventListener('resize', resolve)
})
p.then(() => alert('resize'))

// 只有第一次resize的时候,会弹出alert

五 、实现promise

可以把promise看作事件监听,当处于完成状态,调用then方法。当失败时,会调用catch方法,针对以上对promise的理解,我们可以来写写promise(为了简化,只写了成功状态)

  1. then函数,接受一个函数,根据函数的返回结果,来判断当前promise的状态
  2. resolve函数,接受一个值,如果是普通值,直接调用注册的then函数,如果值是promise,会等待这个promise的状态,根据promise的状态判断调用then还是catch,比如下面这个例子
new Promise((resolve, reject) => {
  resolve(Promise.reject(1))
}).then(res => console.log('res', res)).catch(e => console.log('ee'))
在这里调用的是catch,而不是then

简单promise实现

function MyPromise (resolver) {

  if (typeof resolver !== 'function') {
    throw new TypeError('Promise resolver ' + resolver + ' is not a function')
  }
  if (!(this instanceof MyPromise)) return new MyPromise(resolver);

  var self = this;
  self.status = 'pending';
  self.onFulfilledCallbacks = [];  

  const resolve = (res) => {

    var resStatus = Promise.resolve(res) // 
    if(resStatus === 'fulfilled') {
        setTimeout(() => {
          self.status = 'fulfilled';
          self.data = res;
          this.onFulfilledCallbacks.map(callback => callback.onResolved(res));
    });
    }
    
  }
  resolver(resolve);

}

MyPromise.prototype.then = function(onFulfilled) {
  var self = this;
  var promise2;
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (v) => v
  // 决定新返回的promise状态
  const resolvePromise = (promise, x, resolve, reject) => {
    var thenCalledOrThrow = false;
    if (x === promise) {
      return reject(new TypeError('Chaining cycle detected for promise!'))

    } else if ((x !== null) && (typeof x === 'object' || typeof x === 'function')) {
      try {
        if (typeof x.then === 'function') {
 
          try {
            x.then.call(x, function rs(y) {
      
            if (thenCalledOrThrow) return;
            thenCalledOrThrow = true;
            return resolvePromise(promise, y, resolve, reject);
            });
          } catch(e) {
            Promise.reject(e);
          }
        } else {
          resolve(x);
        }

      } catch(e) {
        if (thenCalledOrThrow) return;
        thenCalledOrThrow = true;
        reject(e);
      }
    } else {
      return resolve(x);
    }
   
  }

  if (self.status === 'pending') {
    return promise2 = new MyPromise((resolve, reject) => {
      self.onFulfilledCallbacks.push({
        onResolved: function (val) {
          try {
            const x = onFulfilled(val);
            resolvePromise(promise2, x, resolve, reject);
    
          } catch (e) {
            reject(e);
          }
        }
      });
    });
  }

 // 要保证then始终是异步调用的,这样代码的执行顺序才不会乱,因为new Promise()中可能有同步代码
  if (self.status === 'fulfilled') {
    return new MyPromise((resolve, reject) => {
      setTimeout(() => {
        try {
          const x = onFulfilled(val);
          resolvePromise(promise2, x, resolve, reject);
  
        } catch (e) {
          reject(e);
        }
      });
    });
  }
  
}
var promise1 = new MyPromise((resolve) => setTimeout(() => resolve('success'))).then((res) => console.log('res-----', res));
console.log('promise1', promise1);

setTimeout(() => {
  console.log('promise1----', promise1);
}, 1000);