zoukankan      html  css  js  c++  java
  • promise, async和await

    最开始实现异步的方法:回调函数

    method1(function(err, result) {
        if (err) {
            throw err;
        }
        method2(function(err, result) {
            if (err) {
                throw err;
            }
            method3(function(err, result) {
                if (err) {
                    throw err;
                }
                method4(function(err, result) {
                    if (err) {
                        throw err;
                    }
                    method5(result);
                });
            });
        });
    });

    回调地狱一层一层嵌套多个回调函数,会使代码错综复杂,难以理解和调试。

    promise

    promise是异步编程的一种解决方案,比传统的解决方案--回调函数和事件,更合理更强大。所谓promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,promise是一个对象,可以获取异步操作的消息,提供统一的API,各种异步操作都可以用同样的方法进行处理。

    特点:1、对象状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。

    2、一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfiled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。如果改变已经发生了,你再对promise对象添加回调函数,也会立即得到这个结果。这与事件完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
    有了promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,promise对象提供统一的借口,使得控制异步操作更加容易。
    基本用法:
    promise对象是一个构造函数,用来生成promise实例。
    const promise = new Promise(function(resolve, reject) {
      // ... some code
    
      if (/* 异步操作成功 */){
        resolve(value);
      } else {
        reject(error);
      }
    });
    resolve和reject是两个函数,由javascript引擎提供,不用自己部署。
    resolve函数作用:将promise对象的状态从未完成变为成功(即从pending变为resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去。
    reject函数作用:将promise对象的状态从未完成变为失败(即从pending变为rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
    Promise实例生成后用then方法分别指定resolved状态和rejected状态的回调函数。
    
    
    
    
    promise.then(function(value){
      // success
    }, function(error){
      // failure
    })
    // then方法的第二个参数可选,不一定要提供,
    function timeout(ms){
     return new Promise((resolve, reject) => {
        setTimeout(resolve('done'), ms);
      });
    }
    
    timeout(100).then(function(value){
       console.log(value);
    }, function(error){
       throw error;
    })

     如果调用resolve函数和reject函数时带有参数,那么它们的参数会被传递给回调函数。reject函数的参数通常是Error对象的实例,表示抛出的错误;resolve函数的参数除了正常的值外,还可能是另一个Promise实例。

    const p1 = new Promise(function(resolve, reject){
      setTimeout(() => reject(new Error('fail')), 3000);
    });
    const p2 = new Promise(function(resolve, reject){
      setTimeout(() => resolve(p1), 1000);
    });
    p2.then(result => console.log(result)).catch(error => console.log(error));
    // Error: fail
    p1 是一个 Promise,3 秒之后变为 rejected。p2 的状态在 1 秒之后改变,resolve 方法返回的是 p1。由于 p2 返回的是另一个 Promise,导致 p2 自己的状态无效了,由 p1 的状态决定 p2 的状态。所以,后面的 then 语句都变成针对后者 p1。又过了 2 秒,p1 变为rejected,导致触发 catch 方法指定的回调函数。
    注:调用resolve或reject并不会终结promise的参数函数的执行。
    new Promise((resolve, reject) => {
       resolve(1);
       console.log(2);
    }).then(r => {
       console.log(r);
    })
    // 2
    // 1

    调用resolve(1)以后,后面的console.log(2)继续执行,并且会先打印出来,因为立即resolved的promise是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。一般来说,调用resolve或reject以后,promise的使命就完成了,后续操作应该放到then方法里面,而不应该直接写在resolve或reject后面,所以最好在它们前面加上return语句,这样就不会有意外。

    new Promise((resolve, reject) => {
      return resolve(1);
      // 后面的语句不会执行
      console.log(2);
    })

    finally()

    finally方法用于指定不管promise对象最后状态如何,都会执行的操作。

    promise
     .then(result => {...})
     .catch(error => {...})
     .finally(() => {...});

    响应多个promise

    Promise.all()方法,接收单个可迭代对象(如数组)作为参数,并返回一个promise。这个可迭代对象的元素都是promise,只有在它们都完成后,所返回的promsie才会被完成。

    let p1 = new Promise(function(resolve, reject) {
        resolve(42);
    });
    let p2 = new Promise(function(resolve, reject) {
        resolve(43);
    });
    let p3 = new Promise(function(resolve, reject) {
        resolve(44);
    });
    let p4 = Promise.all([p1, p2, p3]);
    p4.then(function(value) {
        console.log(Array.isArray(value));  // true
        console.log(value[0]);              //  42
        console.log(value[1]);              //  43
        console.log(value[2]);              //  44
    });

    Promise.race()方法:接收一个可迭代对象作为参数,并返回一个新的promise,一旦来源promise中有一个被解决,所返回的promise就会立刻被解决。

    let p1 = Promise.resolve(42); //等价于p1=new Promise(resolve => resolve(42));
    let p2 = new Promise(function(resolve, reject) {
        resolve(43);
    });
    let p3 = new Promise(function(resolve, reject) {
        resolve(44);
    });
    let p4 = Promise.race([p1, p2, p3]);
    p4.then(function(value) {
        console.log(value);     // 42
    });

    Promise缺点

    1、无法取消Promise,一旦新建它就会立即执行,无法中途取消。

    2、如果不设置回调函数,promise内部抛出的错误,不会反应到外部。

    3、当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

    async

    async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。

    async函数返回一个Promise对象,async函数内部return语句返回的值,会成为then方法回调函数的参数

    async function f(){
      return 'hello world';
    }
    f().then(v => console.log(v));
    // "hello world"

    async函数内部抛出的错误,会导致返回的promise对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到。

    async function f(){
      throw new Error('出错了');
    }
    f().catch(e => console.log(e))
    // Error:出错了
    async 函数返回的 Promise 对象,必须等到内部所有 await 命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到 return 语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。
    async function asyncTest() {
        let getUrl = await fetch();
        console.log(getUrl);
        let downloadUrl = await download();
        console.log(downloadUrl);
        return 'complete';
    }
    asyncTest().then(console.log)
    
    function fetch() {
        return new Promise(function(resolve, reject) {
            setTimeout(function() {
                resolve('fetchDone');
            }, 1000);
        });
    }
    
    function download() {
        return new Promise(function(resolve, reject) {
            setTimeout(function() {
                resolve('downloadDone');
            }, 2000);
        });
    }

    上面代码模拟了一个抓取和一个下载,只有这两个操作全部完成,才会执行then方法里面的console.log

    await

    正常情况下,await命令后面是一个Promise对象,如果不是,会被转成一个立即resolve的Promise对象。

    async function f(){
       return await 123;     //await命令后面的参数是数值123,它被转成Promise对象,并立
                                    // 即resolve
    }
    f().then(v => console.log(v))
    //123

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

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

    注意上面代码中await语句前面没有return,但是reject方法的参数依然传入了catch方法的回调函数。如果在await前面加上return,效果一样。

    只要一个await语句后面的Promise变为reject,那么整个async函数都会中断执行。

    async function f(){
       await Promise.reject('出错了');
       await Promise.resolve('hello world');   //不会执行
    }

    如果我们希望即使前一个异步操作失败,也不要中断后面的异步操作。这时可以将第一个await放try...catch结构里面,这样不管这个异步操作是否成功,第二个await都会执行。

    async function f(){
      try {
         await Promise.reject('出错了');
      } catch(e){}
    
      return await Promise.resolve('hello world');
    }
    f().then(v => console.log(v));
    // hello world
    另一种方法是await后面的Promise对象再跟一个catch方法,处理前面可能出现的错误。
    async function f(){
      await Promise.reject("出错了")
               .catch(e => console.log(e));
       return await Promise.resolve('hello world');
    }
    f().then(v => console.log(v))
    //出错了
    // hello world

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

    async function myFunction() {
      try {
        await somethingThatReturnsAPromise();
      } catch (err) {
        console.log(err);
      }
    }
    
    // 另一种写法
    
    async function myFunction() {
      await somethingThatReturnsAPromise()
      .catch(function (err) {
        console.log(err);
      });
    }

    多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。

    // 写法一
    async function test() {
        let [foo, bar] = await Promise.all([getFoo(), getBar()]);
    }
    
    function getFoo() {
        return new Promise(function(resolve, reject) {
            setTimeout(function() {
                console.log('getFoo');
                resolve()
            }, 2000);
        }) 
    }
    
    function getBar() {
        return new Promise(function(resolve, reject) {
            setTimeout(function() {
                console.log('getBar');
                resolve()
            }, 2000);
        }) 
    }
    
    test()
    
    // 写法二
    async function test() {
        let fooPromise = getFoo();
        let barPromise = getBar();
        let foo = await fooPromise;
        let bar = await barPromise;
    }

    await命令只能用在async函数中,如果用在普通函数,就会报错。

    async function test() {
        let promises = [function1, function2, function3]
    
        promises.forEach(function (fun) {
            const t = await fun();
            console.log(t)
        });
       //Uncaught SyntaxError: await is only valid in async function
    }
    
    function function1() {
        return new Promise(function(resolve, reject) {
            setTimeout(function() {
                resolve('function1')
            }, 2000)
        })
    }
    
    function function2() {
        return new Promise(function(resolve, reject) {
            setTimeout(function() {
                resolve('function2')
            }, 2000)
        })
    }
    
    function function3() {
        return new Promise(function(resolve, reject) {
            setTimeout(function() {
                resolve('function3')
            }, 2000)
        })
    }
    
    test()

    如果将forEach方法的参数改成async函数,也有问题

    promises.forEach(async function (fun) {
        const t = await fun();
        console.log(t)
    });

    上面代码可能不会正常工作,原因是这时三个异步操作将是并发执行,也就是同时执行,而不是继发执行。正确的写法是采用 for 循环。

    for (let i of promises) {
        const t = await i()
        console.log(t)
    }

    如果希望多个请求并发执行,可以使用Promise.all方法,当三个请求都resolved时,下面两种写法效果相同。

    async function test() {
        let promises = [function1(), function2(), function3()]
        let results = await Promise.all(promises);
        console.log(results)
    }
  • 相关阅读:
    faster with MyISAM tables than with InnoDB or NDB tables
    w-BIG TABLE 1-toSMALLtable @-toMEMORY
    Indexing and Hashing
    MEMORY Storage Engine MEMORY Tables TEMPORARY TABLE max_heap_table_size
    controlling the variance of request response times and not just worrying about maximizing queries per second
    Variance
    Population Mean
    12.162s 1805.867s
    situations where MyISAM will be faster than InnoDB
    1920.154s 0.309s 30817
  • 原文地址:https://www.cnblogs.com/xuepei/p/12016344.html
Copyright © 2011-2022 走看看