Promise、generator、async、await

145 阅读7分钟

Promise、generator、async、await

仅记录自己的学习过程

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第 4 天,点击查看活动详情

1.Promise

Promise 是一个构造函数,用来创建一个Promise对象。是ES6对异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合理更强大。

Promise对象代表一个异步操作,有三种状态:

pending(进行中)fulfilled(成功)rejected(失败)

Promise 对象的状态改变有两种:
   
从 pending 变为 fulfilled

从 pending 变为 rejected
var p = new Promise(function(resolve, reject) {

  // do something ...

  if (/* 异步操作成功 */){

    resolve(value);

  } else {

    reject(error);

  }

});

(如果resolve和reject后没有传参,意味着没有返回值,返回undefined,一旦改变就不会再改变)

resolve和reject

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject,由 JavaScript 引擎提供,不用自己部署。resolve 函数的作用是,将Promise对象的状态从“进行中”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去。 reject 函数的作用是,将Promise对象的状态从“进行中”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

Promise实例的then方法:

第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数,方法返回的是一个新的Promise实例,Promise实例的catch方法:用于指定发生错误时的回调函数,可以捕获then里面的错误。 then执行完之后会返回一个新的promise对象

Promise静态方法

Promise.all 

可以将多个Promise实例(对象)包装成一个新的Promise实例(对象)。

(有三个数据接口,如何保证每个接口都请求完成的情况下,如何保证每个接口都完成的情况下执行后面的操作??

1.它接受一个数组作为参数。

2.数组可以是Promise对象,也可以是其它值,只有Promise会等待状态改变。

3.当所有的子Promise都完成,该Promise完成,返回值是全部值的数组。

4.如果有任何一个失败,该Promise失败,返回值是第一个失败的子Promise的结果,(其他的不打印,并且遇到第一个失败的就会马上返回,不会等待其他未完成的promise对象)。

Promise.all([p1,p2,p3]).then(function (arr){

    console.log(arr);

},function (error){

    console.log(error);

});

Promise.race 

类似于Promise.all,区别在于它有任意一个完成就算完成(无论成功或失败),(返回的promise对象状态取决于最先完成的promise对象的状态)

Promise.resolve 

返回一个'成功'状态的Promise对象

let p1 = Promise.resolve(123);

p1.then((value) => {

  console.log('成功:'+value);

},(value) => {

  console.log('失败:'+value);

});

Promise.reject 

返回一个'失败'状态的Promise对象

let p2 = Promise.reject(123);
p2.then((value) => {
  console.log('成功:'+value);
},(value) => {
  console.log('失败:'+value);
});

promise的优点 1.解决回调地狱问题 2.更容易操作异步操作 promise的优点 1.promise无法翻遍,一旦新建,中途无法取消 2.如果不设置回调函数,Promise内部的错误不会反映到外部 3.处于pending状态,不知道进展到哪一个阶段

Generator 

Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。 语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。形式上,Generator 函数是一个普通函数,但是有两个特征: 一是,function关键字与函数名之间有一个星号 *;二是,函数体内部使用yield表达式,定义不同的内部状态(yield 即“产出”)。用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象, 也就是我们遍历器对象(Iterator Object)

// Generator 遍历器生成函数

functionmyGenerator(){

    yield 'hello';

    yield 'world';

    yield 'result';

}

var iterator = myGenerator();

(var iterator = arr[Symbol.iterator]() generator函数 )

console.log( iterator ); // 遍历器对象(Iterator Object)//fn {<suspended>} adj. 悬浮的;暂停的,缓期的(宣判)

console.log( iterator.next() ); // {value: "hello", done: false}(next方法取得的值取决于yield后面的值)

console.log( iterator.next() ); // {value: "world", done: false}

console.log( iterator.next() ); // {value: "result", done: false}

console.log( iterator.next() ); // {value: undefined, done: true}

执行 Generator 函数会返回一个遍历器对象,是一个遍历器对象生成函数。

每次调用遍历器对象的next方法,就会返回一个有着value和done两个属性的对象。

value表示当前的内部状态的值,是yield后面那个表达式的值;done是一个布尔值,表示是否遍历结束。

Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,其实是提供了一种可以暂停执行的函数。yield表达式就是暂停标志。

for...of循环,扩展运算符(...)等方法,内部调用的都是遍历器接口。

任意一个对象的Symbol.iterator方法(数组,字符串, Set,Map,元素集合,arguments),等于该对象的遍历器生成函数( Generator),调用该函数会返回该对象的一个遍历器对象。

原生js对象不具备 Iterator 接口,无法使用上述接口,将 Generator 函数加到对象的Symbol.iterator属性上面即可使用。

ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具有Symbol.iterator属性,就可以认为是“可遍历的”(iterable)。Symbol.iterator属性本身是一个函数,就是当前数据结构默认的遍历器生成函数。执行这个函数,就会返回一个遍历器。至于属性名Symbol.iterator,它是一个表达式,返回Symbol对象的iterator属性,这是一个预定义好的、类型为 Symbol 的特殊值,所以要放在方括号内

functionobjGenerator() {

    let keys = Object.keys(this); // ["name", "sex", "aeg"]

    for (let key of keys) {

        yield [key, this[key]]; // [key,value]

    }

}
let xmObj = { name: 'xiaoming', sex: '男', aeg: 23 };
// 将 Generator 函数加到对象的Symbol.iterator属性上面
xmObj[Symbol.iterator] = objGenerator;
for (let [key, value] of xmObj) { // 可以使用for/of遍历
    console.log(`${key}: ${value}`);
}
// name: xiaoming
// sex: 男
// aeg: 23

console.log( ...xmObj )

async和await

async函数返回一个 Promise 对象(成功状态),可以使用then方法添加回调函数。

async函数内部return语句返回的值,会成为then方法回调函数的参数。

async function fn() {

    return 'hello world';
}
fn().then(v => console.log(v)); // "hello world"

console.log('我先执行'); // "我先执行"

console.log( fn() ); // Promise {<resolved>: "hello world"}
async 函数有多种使用形式。

// 函数声明

async function foo() {}
// 函数表达式
let foo = async function () {};
// 对象的方法
let obj = { async foo() {} };
obj.foo().then(...)
// Class 的方法
class Storage {
    constructor() {
        this.cachePromise = caches.open('avatars');
    }
    async getAvatar(name) {
        const cache = await this.cachePromise;
        return cache.match(`/avatars/${name}.jpg`);
    }
}
const storage = new Storage();
storage.getAvatar('jake').then(…);
// 箭头函数
let foo = async () => {};
// 立即执行函数
(async function (){
    // ...
})();//匿名函数前加async,原因在于async会配合await一起使用,await只能在async中使用,使用了await之后,await之后的表达式(promise)及后面的代码无论是同步还是异步会按照书写顺序执行

async函数内部抛出错误,导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到。
async function fn() {
    throw new Error('出错了');
}
fn().then(
    v => console.log(v),
    e => console.log(e) // catch Error: 出错了
)

await 命令

正常情况下,await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。

async function fn() {

    return await 123;

    // 等同于

    // return 123;

}

fn().then( v => console.log(v) );

// 123

上面代码中,await命令的参数是数值123,这时等同于return 123。 注意,await一定要运行在async 函数内!

当async函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行后面的语句。

(async function (){

    var res1 = await new Promise(function (resolve,reject){

        setTimeout(function (){

            console.log('异步任务1');
            resolve('成功1');
        },1000);
    });
    var res2 = await new Promise(function (resolve,reject){

        setTimeout(function (){

            console.log('异步任务2');

            resolve('成功2');

        },1000);

    });

    console.log(res1,res2);

    console.log('同步任务3');

    console.log('同步任务4');

})();

 await 会等待这个 Promise 完成,并将其 resolve 的结果返回出来。(等待的是成功的结果)

await命令后面的 Promise 对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到。

async function fn() {

    await Promise.reject('出错了');

}

fn().then(v => console.log(v)).catch(e => console.log(e))

// 出错了

任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。

async function fn() {
    await Promise.reject('出错了');
    await Promise.resolve('hello world'); // 不会执行
}
fn().then(
    v => console.log(v),
    err => console.log(err)
);

// '出错了' await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try...catch代码块中。

async function f() {
    try {
        await Promise.reject('出错了');
    } catch(e) {
        console.log(e);
    }
    return await Promise.resolve('hello world');
}
f().then(v => console.log(v));
// hello world

async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。

(async function (){
    var res1 = await promiseAjax({
       type: 'get',
        url: 'datas.php',
        data: 'userid=abc1001'
    });
    var res2 = await promiseAjax({
        type: 'get',
        url: 'datas.php',
        data: 'userid=abc1002'
    });
    var res3 = await promiseAjax({
        type: 'get',
        url: 'datas.php',
        data: 'userid=abc1003'
    });
    res1 = JSON.parse(res1);
    res2 = JSON.parse(res2);
    res3 = JSON.parse(res3);
    // con.innerHTML = `姓名:${res1.name},身份证:${res2.idcode},地址:${res3.address}`;
    return `姓名:${res1.name},身份证:${res2.idcode},地址:${res3.address}`;
})().then(val=>{
    con.innerHTML = val;
});

返回的promise对象取决于内部return或内部的

async函数对 Generator 函数的改进,体现在以下四点。

(1)内置执行器。

Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。

asyncReadFile();

上面的代码调用了asyncReadFile函数,然后它就会自动执行,输出最后结果。这完全不像 Generator 函数,需要调用next方法,或者用co模块,才能真正执行,得到最后结果。

(2)更好的语义。

async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。

(3)更广的适用性。

co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。

(4)返回值是 Promise。

async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。

进一步说,async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。