一文搞懂Promise、async/await、Generator

379 阅读7分钟

一、Promise

js中会有大量的异步操作,但这些异步操作中可能会出现层层嵌套的问题(回调地狱),不会有错但随着代码体积的增加使后续维护会非常困难,使用promise可以很好的解决回调地狱的问题,使代码可读性变得非常之高

如(回调地狱):

function getTea(fn) {
    setTimeout(() => {
        fn('奶茶');     //3秒后调用传进来的函数
    }, 3000);
}
function getHotpot(fn) {
    setTimeout(() => {
        fn('火锅')
    }, 800);
}
//如果想调用完getTea后调用getHotpot:
getTea(function (data) {
    console.log(data)
    getHotpot(function (data) {
        console.log(data)
        //按照顺序,吃完火锅再进行下一项,就形成了回调地狱。。。
    })
})

什么是promise?用来解决什么问题?

  • 从语义来讲,Promise是承诺的意思,承诺它过一段时间会给你一个结果;
  • 从语法来讲,promise是一个对象,从它可以获取异步操作的消息;
  • Promise 是一种解决异步编程的方案,相比回调函数和事件更合理和更强大。

promise的三种状态

  • pending:初始状态也叫等待状态,一般不会见到
  • resolve:成功状态
  • rejected:失败状态

除了异步操作的结果,任何其他操作都无法改变这个状态。创造promise实例后,它会立即执行。

promise的特点

  • 状态不受外界影响
  • 状态一旦改变,就不会再变,任何时候都可以得到这个结果,状态不可以逆

Promise的缺点

  • 无法取消Promise,一旦新建它就会立即执行,无法中途取消
  • 如果不设置回调函数,Promise内部抛出的错误,不会反映到外部
  • 当处于pending状态时,无法得知目前进展到哪一个阶段,是刚刚开始还是即将完成

promise使用场景

在请求一组接口的时候,比如b接口依赖于a接口请求成功之后才能执行,这个时候promise就很有用

使用

function getTea() {
    return new Promise(function (resolve, reject) {
        setTimeout(() => {  //将异步操作放在promise的回调里面,通过resolve或者reject抛出结果
            resolve('奶茶');
        }, 3000);
    });
}
function getHotpot() {
    return new Promise(function (resolve, reject) {
        setTimeout(() => {
            resolve('火锅');
        }, 100);
    });
}

function getCoffee() {
    return new Promise(function (resolve, reject) {
        setTimeout(() => {
            reject('咖啡:失败!');
        }, 1000);
    });
}

then

getTea().then(data => {     //第一个回调函数捕获成功的结果
    console.log(data); 
}, error => {       //第二个回调函数捕获失败的结果
    console.log(error);
});
//奶茶

catch

getCoffee().catch(data => {     //回调函数捕获失败的结果
    console.log(data);
});
//咖啡:失败!

finally

无论异步操作成功或失败,都会执行的回调函数,并返回一个新的Promise对象。它在Promise链中的最后执行,并且不接收任何参数

getTea().then(data => {
    console.log(data); 
}).catch(() => {
    console.log('失败');
}).finally(() => {
    console.log('结束'); 
});
//奶茶  结束

all

all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先失败状态的结果

//都为成功的promise时:
Promise.all([getTea(), getHotpot()]).then(result => {
    console.log(result);   //['火锅','奶茶']
}).catch(err => {
    console.log(err);
});

//包含失败时:
Promise.all([getTea(), getHotpot(), getCoffee(),getCoffee()]).then(result => {
    console.log(result);
}).catch(err => {
    console.log(err);  //咖啡:失败!
});

使用promise优化上述代码所产生的回调地狱:

getTea().then(data => {
    console.log(data);  //奶茶
    return getHotpot();   //return一个还想调的promise对象
}).then(data => {   //这个then调用的是上一个的返回值promise即 getHotpot();
    console.log(data);  //火锅
    return getCoffee();
}).then(data => { 
    console.log(data);
}).catch(err => {
    console.log(err);   //咖啡:失败!
});

二、saync/await

async是ES7的与异步操作有关的关键字,用于声明一个函数是异步的,它返回一个Promise对象,await操作符用于等待一个异步方法执行完成,它只能在异步函数async function内部使用。async/await的目的是简化使用多个promise时的同步行为,并对一组Promises执行某些操作。如果在函数中return一个值,async会把这个值通过Promise.resolve()封装成Promise对象

  • 如果你没有在async函数中写return,那么这个 async 函数返回的是一个值为 undefined 的 成功 状态的 Promise
  • 如果写了return,那么return的值就会作为你 成功 状态的 Promise 的值
  • 如果 async 函数里 return 的是一个 Promise,那么返回的就是这个 Promise 执行成功后的结果

使用

// new Promise((resolve,reject)=>{
//     resolve()
// })
//以上代码简写为: Promise.resolve()

async function fun() {}     //声明async函数  没有return  会将undefined作为promise的结果
fun().then((ok) => {
    console.log(ok)     //undefined
})

-------------------------------------------------------------

function fun1() {
    return new Promise((resolve, reject) => {
        resolve("成功的信息")
    })
}
function fun2() {
    return new Promise((resolve, reject) => {
        resolve("成功的信息2")
    })
}
async function showfun1() {
    let ok = await fun1();      //await fun1()代表fun()1成功的结果
    console.log(ok);
    let ok2 = await fun2();
    console.log(ok2);
} 
showfun1()    //成功的信息  成功的信息2

关于await

await 等的是一个 成功 状态的 Promise

await 等到之后,做了一件什么事情?

右侧表达式的结果,就是await要等的东西。等到之后,对于await来说,分2个情况(1、不是promise对象 2、是promise对象):

  1. 如果不是 promise , await会阻塞后面的代码,先执行async外面的同步代码,再回到async内部,把这个非promise的东西,作为 await表达式的结果。
  2. 如果它等到的是一个 promise 对象,await 也会暂停后面的代码,先执行async外面的同步代码,等着 Promise 对象 fulfilled(成功),然后把 resolve 的参数作为 await 表达式的运算结果。
  • 如果asycn里的代码都是同步的,那么这个函数被调用就会同步执行
async function fn(){
  console.log('a')
}
fn()
console.log('b')
//a
//b
  • 如果在await后面接的这个promsie都是同步的,后面的promise会同步执行,但是拿到这个值还是得等待(特别注意:如果promise没有一个成功的值传入,对await来说就算是失败了,下面的代码就不会执行),所以不管await后面的代码是同步还是异步,await总是需要时间,从右向左执行,先执行右侧的代码,执行完后,发现有await关键字,于是让出线程,阻塞代码
function fn() {
    return new Promise(resolve => {
        console.log(1)
    })
}
async function f1() {
    await fn()
    console.log(2)
}
f1()
console.log(3)
//1
//3
//这个代码因为fn是属于同步的,所以先打印出1,然后是3,但是因为没有resolve结果,所以await拿不到值,因此不会打印2


function fn() {
    return new Promise(resolve => {
        console.log(1)
        resolve()
    })
}
async function f1() {
    await fn()
    console.log(2)
}
f1()
console.log(3)
//1
//3
//2
//这个代码与前面相比多了个resolve说明promise成功了,所以await能拿到结果,因此就是1 3 2

三、generator

主要是异步编程,是一个异步任务的容器,一个generator看上去像一个函数,但可以返回多次,通过yield关键字,把函数的执行流挂起,为改变执行流程提供了可能,从而为异步编程提供解决方案。

function fun() {
    console.log(1);
    console.log(2);
    console.log(3);
}
fun();     //调用的时候全部执行  1 2 3

generator方式:

function* fun() {
    console.log(1);
    yield      //异步不同阶段的分割线
    console.log(2);
    console.log(3);
}
fun();  //未执行打印任何内容
//使用next来执行generator函数
let gen = fun();    //直接赋值不调用
gen.next();     //1
gen.next();     //2  3

执行机制

  • 在函数名后面加上()即可,但是 Generator 函数不会像普通函数一样立即执行,而是返回一个指向内部状态对象的指针,所以要调用遍历器对象Iterator 的 next 方法,next()方法被首次(后续)调用时,其内的语句会执行到第一个(后续)出现yield的位置为止,yield后紧跟迭代器要返回的值,也就是指针就会从函数头部或者上一次停下来的地方开始执行到下一个yield
  • next()方法返回一个对象,这个对象包含两个属性:value和done,value属性表示本次yield表达式的返回值,done属性为布尔类型,表示生成器后续是否还有yield语句

当 next 传入参数的时候,该参数会作为上一步yield的返回值:

function* f(x) {
    let y = yield x + 10;
    console.log(y);
    yield x + y;
    console.log(x, y);
    return x + 30;    //return 方法返回给定值,并结束遍历 Generator 函数。
}
let g = f(1);
console.log(g); // f {<suspended>}
console.log(g.next()); // {value: 11, done: false}
console.log(g.next(50)); // {value: 51, done: false}  // 上一步y被赋值为50
console.log(g.next()); // {value: 31, done: true}  // x,y 1,50
console.log(g.next()); // {value: undefined, done: true} // 可以无限next(),但是value总为undefined,done总为true

使用场景

function* objectEntries(obj) {
    const propKeys = Reflect.ownKeys(obj);     //返回对象所有的属性,不管属性是否可枚举,包括 Symbol
    for (const propKey of propKeys) {
        yield [propKey, obj[propKey]];
    }
}
const jane = { first: 'Jane', last: 'Doe' };
for (const [key, value] of objectEntries(jane)) {
    console.log(`${key}: ${value}`);
}
// first: Jane// last: Doe

jane 原生是不具备 Iterator 接口无法通过 for...of遍历。这边用了 Generator 函数加上了 Iterator 接口,所以就可以遍历 jane 对象了。