ES6+新增常用内容总结(五)

323 阅读9分钟

这是我参与8月更文挑战的第9天,活动详情查看:8月更文挑战

Promise

Promise介绍

Promise是异步编程的一种解决方案,从语法上来看Promise是一个对象,从它可以获取异步操作的消息,简单来说,它就像是一个容器,里边保存着某个未来结束的事件,通常是获取异步操作的结果。Promise有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败),一旦状态改变就不会在改变,任何时候得到的都是这个结果。Promise状态改变无非两种结果,一种是从pendingfulfilled,一种是从pendingrejected

Promise基本语法

在ES6中规定,Promise对象是一个构造函数,用来生成Promise实例

const promise = new Promise(function(resolve,reject){
    if(异步处理结果为成功){
        resolve(value)
    }else{
        reject(error)
    }
})

从上面代码中我们可以看出,Promise构造函数接收了一个函数为参数,该函数有resolvereject两个参数,

resolve函数是当异步操作成功的时候调用,并将异步操作的结果作为参数传递出去,

reject函数是当异步操作失败时调用的,并将错误信息作为参数传递出去,

then

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

promise.then(value=>{
    console.log(value)
},error=>{
    console.log(error)
})

then方法可以接受两个回调函数作为参数,第一个回调函数就是Promise返回成功的回调函数,第二参数就是Promise返回失败的回调函数

多个then

then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。

promise.then(value=>{
    console.log(value)
}).then(value=>{
    console.log(value)
}).then(value=>{
    console.log(value)
})

catch

我们知道Promise对象除了then方法,还有一个catch方法,它是做什么用的呢?其实它和then的第二个参数一样,用来指定reject的回调。用法是这样:

promise.then(value => { 
    console.log('resolved',value); 
}).catch(err => { 
    console.log('rejected',err); 
});

效果和写在then的第二个参数里面一样。不过它还有另外一个作用:在执行resolve的回调(也就是上面then中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死js,而是会进到这个catch方法中。请看下面的代码:

promise.then((data) => { 
    console.log('resolved',data); 
    console.log(error); //这里的error未定义
}) .catch((err) => { 
    console.log('rejected',err); 
});

在resolve的回调中,我们console.log(error);而error这个变量是没有被定义的。如果我们不用Promise,代码运行到这里就直接在控制台报错了,不往下运行了。但是在这里,会得到这样的结果:

image.png

Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。一般来说,不要在then()方法里面定义 Reject 状态的回调函数(即then的第二个参数),总是使用catch方法,更接近同步的写法(try/catch)。因此,建议总是使用catch()方法,而不使用then()方法的第二个参数。

finally

finally方法用于指定不管Promise对象最后的状态如何,在执行完thencatch指定的回调函数以后,都会执行finally方法指定的回调函数

promise.then(value => {
    状态为fulfilled 执行
}).catch(error => {
    状态为rejected 执行
}) .finally(() => {
    不管状态为什么都会执行
});

finally不接收任何参数,这就意味着没办法知道Promise返回的状态如何,那么,finally方法里边的操作与状态无关,不依赖Promise的执行结果

all

Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

//三个状态都成功
    const promise1 = new Promise((resolve,reject)=>{
        resolve('success1')
    })
    const promise2 = new Promise((resolve,reject)=>{
        resolve('success2')
    })
    const promise3 = new Promise((resolve,reject)=>{
        resolve('success3')
    })
    
    const p = Promise.all([promise1,promise2,promise3])
    
    p.then(data=>{ 
        console.log(data); //三个都成功则成功 
    }, error=>{ 
        console.log(error) // 只要有失败,则失败 
    })

image.png

这是上边代码执行出来的结果,现在让我看看有一个失败的会怎样

//三个状态都成功
    const promise1 = new Promise((resolve,reject)=>{
        resolve('success1')
    })
    const promise2 = new Promise((resolve,reject)=>{
        reject('error')
    })
    const promise3 = new Promise((resolve,reject)=>{
        reject('error1')
    })
    
    const p = Promise.all([promise1,promise2,promise3])
    
    p.then(data=>{ 
        console.log(data); //三个都成功则成功 
    }, error=>{ 
        console.log(error) // 只要有失败,则失败 
    })

image.png 通过上面的代码可以看出,p的状态是由promise1 promise2 promise3决定的,分为两种情况:

  1. 只有promise1 promise2 promise3的状态都为fulfilled的时候,那么p的状态也为fulfilled,此时promise1 promise2 promise3的返回值组成一个数组,传给p的回调函数
  2. 只要 promise1 promise2 promise3 之中有一个被 rejected,p的状态就变成 rejected,此时第一个被reject 的实例的返回值,会传递给p的回调函数。

race

Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

    const promise1 = new Promise((resolve,reject)=>{
       setTimeout(() => { 
           resolve('成功'); 
       }, 2000);
    })
    const promise2 = new Promise((resolve,reject)=>{
         setTimeout(() => { 
           reject('失败'); 
       }, 5000);
    })
    
    const p = Promise.race([promise1,promise2])
    
    p.then(data=>{ 
        console.log(data); 
    }, error=>{ 
        console.log(error)
    })

image.png

从上边代码的结果可以看出,只要promise1 promise2 之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

resolve

有时需要将现有对象转为 Promise 对象,Promise.resolve方法就起到这个作用。

resolve方法的参数分为四种情况:

  1. 参数是一个Promise实例的

    resolve方法不做任何的处理,原封不动返回

  2. 参数是一个具有then方法的对象

    let thenable = {
      then: function(resolve, reject) {
        resolve(42);
      }
    };

    let p1 = Promise.resolve(thenable);
    p1.then(function (value) {
      console.log(value);  // 42
    });

resolve方法会将这个对象转为Promise对象,然后立即执行对象里边的then方法 3. 参数不是具有then()方法的对象,或根本就不是对象

   const p = Promise.resolve('Hello');

   p.then(function (s) {
     console.log(s)
   });
   // Hello

如果参数是一个原始值,或者是一个不具有then方法的对象,则resolve方法返回一个新的 Promise 对象,状态为resolved。

  1. 不带参数的
const p = Promise.resolve();

p.then(function () {
  // ...
});

resolve方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。

所以,如果希望得到一个 Promise 对象,比较方便的方法就是直接调用resolve方法。

reject

reject方法也会返回一个新的 Promise 实例,该实例的状态为rejected。

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

p.then(null, function (s) {
  console.log(s)
});
// 出错了

上面代码生成一个 Promise 对象的实例p,状态为rejected,回调函数会立即执行。

reject方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。

async await

async/await是JavaScript为了更好的解决异步问题而提出的一种解决方案,许多人将其称为异步的终极解决方案,下面让我们一起来看一下async/await

基本用法

async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句

function timeout(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value);
}

asyncPrint('hello world', 50);

使用注意点

  1. await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try...catch代码块中。
async function myFunction() {
  try {
    await somethingThatReturnsAPromise();
  } catch (err) {
    console.log(err);
  }
}
  1. 多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。
  let foo = await getFoo();
  let bar = await getBar();

上面代码中,getFoogetBar是两个独立的异步操作(即互不依赖),被写成继发关系。这样比较耗时,因为只有getFoo完成以后,才会执行getBar,完全可以让它们同时触发。

可以写成这样:

let [foo, bar] = await Promise.all([getFoo(), getBar()]);
  1. await命令只能用在async函数之中,如果用在普通函数,就会报错
async function dbFuc(db) {
  let docs = [{}, {}, {}];

  // 报错
  docs.forEach(function (doc) {
    await db.post(doc);
  });
}

优雅的错误处理方法

上边我们也说到async返回一个Promise对象,运行结果可能是rejected,所以最好把await命令放在try...catch代码块中,如果是有多个异步操作,需要对每个异步返回的error错误进行不同的处理,我们可以这样写:

const fetchDataA = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('fetch data is A')
        }, 1000)
    })
}

const fetchDataB = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('fetch data is B')
        }, 1000)
    })
}

const fetchDataC = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('fetch data is C')
        }, 1000)
    })
}

(async () => {
    try {
        const dataA = await fetchDataA()
        console.log('dataA is ->', dataA)
    } catch(err) {
        console.log('err is ->', err)
    }

    try {
        const dataB = await fetchDataB()
        console.log('dataB is ->', dataB)
    } catch(err) {
        console.log('err is ->', err)
    }

    try {
        const dataC = await fetchDataC()
        console.log('dataC is ->', dataC)
    } catch(err) {
        console.log('err is ->', err)
    }
})()

这样处理也不是不可以,但是代码里乍一看全是try...catch,看着不是那么优雅,这时我们是不是也想只用一个try...catch

// ... 这里 fetch 函数省略

(async () => {
    try {
        const dataA = await fetchDataA()
        console.log('dataA is ->', dataA)
        const dataB = await fetchDataB()
        console.log('dataB is ->', dataB)
        const dataC = await fetchDataC()
        console.log('dataC is ->', dataC)
    } catch(err) {
        console.log('err is ->', err)
        // 难道要定义 err 类型,然后判断吗??
        /**
         * if (err.type === 'dataA') {
         *  console.log('dataA err is', err)
         * }
         * ......
         * */
    }
})()

如果是这样的话那我们岂不是要写一对if else的判断,这时候我们在想async返回的是Promise对象,那么我们就可以用then方法

(async () => {
    const fetchData = () => {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('fetch data is me')
            }, 1000)
        })
    }

    const data = await fetchData().then(data => data ).catch(err => err)
    console.log(data)
})()

在上面写法中,如果 fetchData 返回 resolve 正确结果时,data 是我们要的结果,如果是 reject 了,发生错误了,那么 data 是错误结果,这样我们就又要判断了,显然不太合适。

(async () => {
    const fetchData = () => {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('fetch data is me')
            }, 1000)
        })
    }

    const [err, data] = await fetchData().then(data => [null, data] ).catch(err => [err, null])
    console.log('err', err)
    console.log('data', data)
    // err null
    // data fetch data is me
})()

这样是不是好很多了呢,但是问题又来了,不能每个 await 都写这么长,写着也不方便也不优雅,并且会有很多的重复代码

(async () => {
    const fetchData = () => {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('fetch data is me')
            }, 1000)
        })
    }

    // 抽离成公共方法
    const awaitWrap = (promise) => {
        return promise
            .then(data => [null, data])
            .catch(err => [err, null])
    }

    const [err, data] = await awaitWrap(fetchData())
    console.log('err', err)
    console.log('data', data)
    // err null
    // data fetch data is me
})()

将对 await 处理的方法抽离成公共的方法,在使用 await 调用awaitWrap 这样看起来是不是就好多了

结语

上面就是我关于Promiseasync/await理解的记录,
以上内容估计还有许多错误之处,如果掘金上的朋友有看到,还望不吝指出!
同时我也希望大家通过观看这篇文章能够有所收获!谢谢您的观看!
      参考文献:
          https://es6.ruanyifeng.com/ 
          https://juejin.cn/post/6844903767129718791