likes
comments
collection
share

【JS八股文】Promise

作者站长头像
站长
· 阅读数 14

讲到Promise就不得不比较一下回调Promise的区别和关系。 JS实现异步最常用的方式就是使用回调PromiseES6中才引入的概念,他主要是为了解决回调存在的一些问题,可以看作回调的升级版。 那回调中存在什么问题,和Promise又有什么区别呢?

回调的问题

1. 信任问题

假设我去奶茶店买珍珠奶茶,我觉得珍珠不够多,想加一份珍珠。于是我跟店员说我加一份珍珠(回调,在奶茶做好后执行),然后服务员帮我做好标记,我付好钱之后,就等着开心的喝奶茶了。大部分情况下,我是可以愉快的获得一份加了珍珠的珍珠奶茶的,但有时也会有忘记加的情况(别问我为什么知道,怨念..),这就是回调可能存在的信任问题,我们不能确定你的回调函数会如何执行,执行几次,以及会不会执行。

// 伪代码
function buyMilkTea() {
  // 制作奶茶,接收一个加料回调
  doMilkTea(addSubject);

  // 加料
  addSubject() {
    // 加一份珍珠
  }
}

Promise是如何解决信任问题的呢? Promise回调不同,他是通过分离回调(一个成功通知,一个错误通知),告诉我他完成了(只会告诉一次),接下来要执行什么,我自己来决定(决定权放在我这)。 还是买奶茶的例子,我付好钱,等待叫号。会有两种结果:

  1. 叫到我的号,奶茶做好了(成功通知)。这时我可以再买一份珍珠自己加(得到通知后,自己决定后面干嘛)
  2. 叫到我的号,但是因为原料不足,我的奶茶做不出来(失败通知)。这时我可以选择换一个品类,或者退钱

回调中,上面的两种情况是写在一个回调函数中的,这样分开写成功和失败的逻辑,也让代码更清晰。

// 伪代码
new Promise((resolve, reject) => {
  buyMilkTea();
  resolve(); // 通知叫号
}).then(
  res => { // 成功回调
    addSubject(); // 买一份珍珠,自己加
  },
  error => { // 失败回调
    // 退钱
  }
)

2. 回调地狱

回调是异步执行的,不能保证回调函数的执行顺序。因此为了保证回调函数的执行顺序,就会使用回调中嵌套回调一层层嵌套下去,实现我们想要的执行顺序。想想这种代码就非常的头疼...

// 伪代码,大概模拟下回调地狱的情况。。。
getUp(function() {
  brushTeeth(function() {
    haveBreakfast(function() {
      goToSchool(function() {
        haveLunch()
        goHome(function() {
          sleep()
        })
      })
    })
    makeUp()
    lookInTheMirror()
  })
})

Promise是如何解决回调地狱的呢? 和信任问题一样,通过只返回一次状态(成功 / 失败),告诉我他做完,接下来要做什么由我来决定。而且Promise的.then中返回会自动创建一个新的Promise来进行链式链接

// 这种形式更符合我们大脑的逻辑
new Promise((resolve,reject) => {
  getUp();
  resolve();
}).then(res => {
  brushTeeth();
  haveBreakfast();
  makeUp();
  lookInTheMirror();
  return;
}).then(res => {
  goToSchool();
  return;
}).then(res => {
  haveLunch();
  return;
}).then(res => {
  goHome();
  return;
}).then(res => {
  sleep();
  return;
});

总结回调是把方法交给别人去执行,Promise是别人告诉你“我执行完了”,然后自己控制下面执行什么

Promise详解

通过上述的讲解,我们也大致了解了Promise解决的问题和为什么要有Promise。所以Promise就像一个法官,对我们执行的内容进行判决(只会判决一次,不能改判),我们得到判决结果(then中获得),对响应的结果进行处理。我们也相当于一个法官,返回一个结果(又一次判决),下面通过.then还能获得我们的判决结果(实现链式调用)

Promise API

Promise 构造函数

  • 必须和new一起使用
  • 必须提供一个函数回调
  • 这个函数接受两个函数回调resolvereject
    • reject拒绝这个Promise
    • resolve既可以完成也可以拒绝
new Promise((resolve, reject) => { // 提供一个函数回调,这个函数有两个回调resolve和reject
  // ... 做些事,这里的内容是同步的
  
  // 完成判决
  resolve();
  
  // 拒绝判决 两种方式
  // resolve(Promise.reject());
  // reject();
});

then( )和catch( )

每个Promise实例都有then()catch()方法,可以通过这两个方法来处理Promise的判决结果(都是微任务,插入任务队列)。

  • then() 有两个参数,第一个是处理完成结果的回调,第二个是处理错误信息的回调
    new Promise((resolve, reject) => {
      resolve(1);
      // 或者
      // reject(2);
    }).then(
      res => {}, // 第一个参数,完成结果的回调
      error => {} // 第二个参数,处理错误信息的回调
    )
    
  • catch() 只有一个参数,处理错误信息的回调。等价于then(null, rejected)
      new Promise((resolve, reject) => {
        // resolve(1);
        // 或者
        reject(2);
      }).catch(
        error => {} // 处理错误信息的回调
      )
    

Promise.resolve( )和Promise.reject( )

  • Promise.reject( ) 快速创建已被拒绝的Promise

    Promise.reject('error')
    .then(
    (res) => {
      // 不会执行
    },
    error => {
      // 执行
      console.log(error); // `error`
    });
    
  • Promise.resolve( ) 快速创建一个已完成/拒绝的Promise

    • 传入的是个数据,创建一个已完成的Promise,并返回传入的数据
    • 如果传入值是含有then()的对象,会获取then()返回的值,并创建Promise返回这个值
    • 传入的是真正的Promise,直接返回这个promise,不会有任何开销
      // 传入的是数据
      Promise.resolve('fulfilled')
      .then(
      (res) => {
        // 执行
        console.log(res); // `fulfilled`
      },
      error => {
        // 不会执行
      });
    
    
      // 传入的是thenable
      var obj = {
        then(callBack) {
          callBack('33');
        }
      }
    
      Promise.resolve(obj)
      .then(
      (res) => {
        console.log(res); // `33`
      });
    
    
    
      // 传入Promise
      var p1 = new Promise((resolve, reject) => {
        resolve('33');
      })
    
      Promise.resolve(p1) // 原封不动返回p1
      .then(res => {
        console.log(res); // `33`
      })
    

通过Promise.resolve()过滤来获得可信任性的/Promise,完全没有坏处

Promise的特点

  • then()的回调是微任务(插入任务队列,进行异步调用)
  • 链式then()会注册很多微任务,然后顺序调用
  • then()会自动创建并返回一个新Promise
  • then()return一个Promise会替换then()自动生成的Promise
  • 两个独立的Promise回调运行顺序无法预测
  • 每个Promise内只能判决一次(保证回调只会执行一次),重复的判决会被抛弃
  • Promise只有一个判决值(resolve() / reject()
  • Promise返回方法只有一个参数resolve() / reject() 只接受一个参数)
  • Promise出现错误,但没有设置处理函数,那么这个错误会顺着then()的链条传播下去,直到这个错误被处理

Promise模式

Promise.all([ ])

数组内的任务全部完成才完成。返回一个Promise,返回值是一个数组。 有一个被拒绝,Promise.all([ ])就会立即被拒绝,并丢弃其它所有Promise的结果。

// 全部成功
var p1 = Promise.resolve('33');
var p2 = Promise.resolve('66');

Promise.all([p1, p2])
.then(res => {
  console.log(res); // ['33', '66']
});
// 有失败成功
var p1 = Promise.resolve('33');
var p2 = Promise.reject('err');

Promise.all([p1, p2])
.then(res => {
  console.log('then'); // 不会执行
})
.catch(err => {
  console.log(err); // 'err'
});

Promise.race([ ])

众多Promise赛跑,只有第一个跨过终点线的Promise被响应,其它抛弃(会把数组中所有的Promise都执行)。一旦有一个Promise决议为拒绝,就会拒绝。

注意

  • 至少有一个参赛者,传入空数组,Promise永远处于pending状态。(永远不要给空数组)
  • 由多个Promisethenable立即值组成。立即值在竞争中无意义,因为显然第一个获胜。
// 有立即值的竞赛
var p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1);
  }, 1000);
});

var p2 = 2;

Promise.race([p1, p2])
.then((res) => {
  console.log(res); // 2
});
// 传入空数组
Promise.race([])
.then((res) => {
  console.log(res); // 不会执行
}); // Promise {<pending>}
// 有拒绝的竞赛
var p1 = Promise.resolve(1);
var p2 = 2;
var p3 = Promise.reject(3);

Promise.all([p1, p2, p3])
.then(res => {
  console.log('then'); // 不会执行
})
.catch(err => {
  console.log(err); // 'err'
});

Promise.any([ ])

只需完成一个(忽略拒绝,不需要数组中的Promise全部执行)。注意和race的区别

Promise相关的面试题

手写实现一个Promise.all()

function promiseAll(arr) {
  if (arr.length == 0) return;
  let res = [];
  let idx = 0; // 保证最后一个promise完成后进行返回
  return new Promise((resolve, reject) => {
    arr.forEach((pro, i) => {
      pro.then(val => {
        idx++;
        res[i] = val;  // 保证结果值与传入的promise的顺序一致,确保promise中有异步的情况下也能正常返回
        if (idx == arr.length) resolve(res);
      }, err => {
        reject(err); // 有一个拒绝,就直接拒绝
      })
    });
  })
}

// 测试
var p1 = Promise.resolve(1);
var p2 = Promise.resolve(2);
promiseAll([p1, p2])
.then(res => {
  console.log(res); // [1, 2]
});

手写实现一个Promise.race()

function promiseRace(arr) {
  // 没有传值报错
  if (!arr) throw(TypeError('undefined is not iterable'));
  // 传入的是空数组,一直保持pending
  if (arr.length == 0) return new Promise((resolve, reject) => {});
  // 正常情况
  return new Promise((resolve, reject) => {
    arr.forEach(p => {
      // 运用Promise.resolve()的特性,解析一下p,保证返回promise
      Promise.resolve(p).then(res => {
        resolve(res);
      }, err => {
        reject(err);
      });
    })
  });
}

// 测试
var p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1);
  }, 1000);
});

var p2 = 2;

promiseRace([p1, p2])
.then((res) => {
  console.log(res); // 2
});

如果觉得内容对你有帮助,请点个关注,你们的鼓励是我持续更新下去的动力,比心。

如需转载请注明出处,感恩。

更多内容可先关注GitHub