ES6 - Promise

Promise API & 手写 Promise

概念

本质上 Promise 是一个函数返回的对象,我们可以在它上面绑定回调函数,这样我们就不需要在一开始把回调函数作为参数传入这个函数了,举个例子:

function B(){};
function C(){};

function A(args,B,C){}; // 函数A接受参数和两个回调函数

转换成 Promise 形式:

A(args).then(B,C)

链式调用

概念

连续执行两个或者多个异步操作是一个常见的需求,在上一个操作执行成功之后,开始下一个的操作,并带着上一步操作所返回的结果。我们可以通过创造一个 Promise 链来实现这种需求。

每个 Promise 对象都可以在其上多次调用 then 方法,then 方法会返回一个全新的 Promise 对象,下一个 then 方法其实是依附于返回的 Promise 对象,这样层层挂钩就形成了 promise 链:

new Promise((resolve,reject) => {
    resolve('start')
}).then(data => {
    console.log(data); // 'start', then 的参数是上一个 Promise 对象的 resolve 的参数
    return Promise.resolve(1);
}).then(data => {
    console.log(data); // 1
})

中断调用

如果要中途跳出链式调用,就需要用reject()抛出异常,用catch()去接受异常,为了和其他报错区别开来,可以定义一个 manualExit 的变量作为 reject 的参数:

new Promise((resolve,reject) => {
    resolve('start')
}).then(data => {
    return Promise.resolve(1);
}).then(data => {
    reject({
        manualExit : true
    })
}).catch(err => {
    if(err.manualExit){
        // 手动退出
    }else{
        // 其他异常
    }
}).then(data => {
    // 依然会执行
})

当 reject 执行时,会跳过后面的 then 方法,直接执行 catch,这里需要注意的是,catch 方法最好放在最后,因为 catch 方法会返回一个新的 resolved 的 Promise 实例,所以在 catch 之后的 then 方法依旧会被执行。

属性

.finally

ES2018 引入标准,不论 Promise 对象最后是何种状态(fulfilled / rejected),都会执行该操作,如果代码需要在成功或者失败两种情况下都运行,写入.finally()中可以减少代码量。

promise
.then(result => {
    console.log('X')
})
.catch(error => {
    console.log('X')
})

// 等同于

promise
.finally(() => {
    console.log('X')
});

.all

const p = Promise.all([p1, p2, p3]);

.all()相当于&&操作符,它接受由多个 Promise 实例组成的数组,当所有实例的状态都变为 fulfilled 时,p 的状态才会变成 fulfilled,只要其中一个实例被 rejected,那么状态就会变成 rejected。

const p1 = new Promise((resolve, reject) => {
  resolve('resolved');
})
.then(result => result)


const p2 = new Promise((resolve, reject) => {
  throw new Error('rejected');
})
.then(result => result)


Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e)); // 由于p2报错,所以会进入catch

如果 p2 设置了 catch,并且返回了错误,那结果就不一样了。catch 方法会返回一个新的 resolved 的 Promise 实例,p2 实际上是指向了这个实例,导致 .all() 方法中两个实例都是 resolved,因此会进入.then()而非.catch()

...

const p2 = new Promise((resolve, reject) => {
  throw new Error('rejected'); // or reject('rejected')
})
.then(result => result)
.catch(e => e);


Promise.all([p1, p2])
.then(result => console.log(result)) // 实际进入的是then
.catch(e => console.log(e)); 

.any

.any()相当于||操作符,它接受由多个 Promise 实例组成的数组,只要有一个实例的状态变为 fulfilled 时,p 的状态就会变成 fulfilled,当所有实例的状态都变为 rejected 时 p 的状态才会变成 rejected。

const resolved = Promise.resolve(2);
const rejected = Promise.reject(-1);
const anyPromise = Promise.any([resolved, rejected]);
anyPromise.then(function (results) {
  console.log(results);  // 2
});

.race

const p = Promise.race([p1, p2, p3]);

.race()接受由多个 Promise 实例组成的数组,当其中一个实例的状态发生改变时,p 的状态就会改变并将率先改变的实例的返回值(不论成功/失败)传递给回调函数。

race 的特性可以用于监听 fetch 请求是否超时:

function _fetch(fetch_promise,timeout){
    return Promise.race([
        fetch_promise,
        new Promise((resolve, reject)=>{
            setTimeout(()=>{
                reject(new Error('timeout'));
            },timeout)
        })
    ]).then(()=>{
      console.log('success')
    }).catch(()=>{
      console.log('error');
    })
}

.allSettled

ES2020 引入标准,.allSettled()接受由多个 Promise 实例组成的数组,返回一个新的 Promise,只有所有的实例都返回结果时(不论 fulfilled 还是 rejected)才会返回每个 Promise 的状态(由 {status,reason/value}组成的数组),要注意的时.allSettled()不会失败。

const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);

const allSettledPromise = Promise.allSettled([resolved, rejected]);

allSettledPromise.then(function (results) {
  console.log(results);
});

// [{status: 'fulfilled', value: 42}, {status: 'rejected', reason: -1}]

.resolve

.resolve()可以将现有对象转换成 promise 对象,有以下几种情况:

  • 对象是 Promise 实例:直接返回;

  • 对象是由 then 方法的实例:转换成 Promise 对象,然后立即执行 then 内的方法;

    let thenable = {
      then: function(resolve, reject) {
        resolve(42);
      }
    };
    
    let p1 = Promise.resolve(thenable);
    p1.then(function(value) {
      console.log(value);  // 42
    });
    
  • 对象不具备 then 方法或者压根不是对象(甚至没有):返回一个新的 Promise 对象,状态为 resolved:

    const p = Promise.resolve('Hello');
    
    p.then(function (s){
      console.log(s)
    });
    // Hello
    
    const p = Promise.resolve();
    
    p.then(function () {
      // ...
    });
    

.reject

.reject(reason)返回一个新的 Promise 实例,该实例的状态为 rejected,传入的 reason 会原封不动的返回。

const p = Promise.reject('rejected');
// 等同于
const p = new Promise((resolve, reject) => reject('rejected'))

p.then(null, function (s) {
  console.log(s)  // rejected
});

手写 Promise

function myPromise(constructor){
    let self=this;
    self.status="pending";  // 定义状态改变前的初始状态
    self.value=undefined;   // 定义状态为resolved的时候的状态
    self.reason=undefined;  // 定义状态为rejected的时候的状态
      self.then=function(onFullfilled, onRejected){
       let self=this;
       switch(self.status){
          case "resolved":
            onFullfilled(self.value);
            break;
          case "rejected":
            onRejected(self.reason);
            break;
          default:       
       }
    }

    function resolve(value){
       if(self.status==="pending"){
          self.value=value;
          self.status="resolved";
       }
    }
    function reject(reason){
       if(self.status==="pending"){
          self.reason=reason;
          self.status="rejected";
       }
    }
    //捕获构造异常
    try{
       constructor(resolve,reject);
    }catch(e){
       reject(e);
    }
}

async/await

async/await 是用来优化 Promise 的回调问题,被称作是异步的终极解决方案。

async:

Promise 的语法糖,返回一个 Promise,如果在函数中 return 一个常量,async 会把这个常量通过 Promise.resolve() 封装成 Promise 对象,如果返回的是 Promise 对象,则以返回的 Promise 为准。

返回常量:

async function hello() { return "Hello" };
hello(); // Promise {<fulfilled>: "Hello"}

返回 Promise:

async function hello() { return Promise.reject("Hello") };
hello(); // Promise {<rejected>: "Hello"}

await:

await 可以放在任何的异步前面,会阻塞代码运行直到其右侧 Promise 完成,然后返回结果值。

async function hello() {
  return greeting = await Promise.resolve("Hello");
};

hello().then(val=>console.log(val)); // Hello
作者

BiteByte

发布于

2020-08-08

更新于

2024-11-15

许可协议