Promise和Async/await

阅读本篇文章之前一定要明白异步解决方案和 http 的关系。异步是异步,http 是http。http 请求是一个异步的过程,而异步并不一定就和 http 请求联系在一起。所以不要提起异步就默认为就是 ajax请求数据。

Promise

Promise 对象用于表示一个异步操作的最终状态(玩成或失败),以及其返回的值。

看下控制台输出的 Promise 对象信息:


promise.png

Promise 的构造函数接受一个函数作为参数。传入的函数参数可以有两个: resolve 和 reject。resolve 是将 Promise 从 pending 状态置为 fulfilled 状态。 reject 是将 Promise 状态从 pending 置为 rejected 状态。我们可以简单的理解成:resolve 表示异步操作成功后的回调函数, reject 表示异步操作失败后的回调函数。

Promise 最直接的好处就是链式调用。

没有使用 Promise 来实现异步操作:

doSomething(function(result) {
  doSomethingElse(result, function(newResult) {
    doThirdThing(newResult, function(finalResult) {
      console.log('Got the final result: ' + finalResult);
    }, failureCallback);
  }, failureCallback);
}, failureCallback);

按上面的写法,做很多的多层回调会让我们陷入经典的回调地狱。

而使用 Promise 来实现异步多层回调:

doSomething()
.then(function(result) {
  return doSomethingElse(result);
})
.then(function(newResult) {
  return doThirdThing(newResult);
})
.then(function(finalResult) {
  console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);

对比于我们可能会陷入的回调地狱,Promise 简化了层层回调的写法。而且,用维护状态、传递状态的方式来使得回调函数能够及时调用,这比传递 callback 函数要简单、灵活的多。

在 then 方法中,我们除了可以 return 一个 Promise 对象,还可以直接 return 数据,可以在之后的 then 中接收到 return 数据了:


function runAsync1(){
    var p = new Promise(function(resolve, reject){
        //做一些异步操作
        setTimeout(function(){
            console.log('异步任务1执行完成');
            resolve('随便什么数据1');
        }, 1000);
    });
    return p;            
}
function runAsync2(){
    var p = new Promise(function(resolve, reject){
        //做一些异步操作
        setTimeout(function(){
            console.log('异步任务2执行完成');
            resolve('随便什么数据2');
        }, 2000);
    });
    return p;            
}

runAsync1()
.then(function(data){
    console.log(data + '@@');
    return runAsync2();
})
.then(function(data){
    console.log(data + '##');
    return '直接返回数据';  //这里直接返回数据
})
.then(function(data){
    console.log(data + '--');
});

上面的例子只是讲了 Promise 的用法,其中涉及到的只有成功时回调的 resolve,那失败状态又是如何使用的呢?

function getNumber(){
    var p = new Promise(function(resolve, reject){
        //做一些异步操作
        setTimeout(function(){
            var num = Math.ceil(Math.random()*10); //生成1-10的随机数
            if(num<=5){
                resolve(num);
            }
            else{
                reject('数字太大了');
            }
        }, 2000);
    });
    return p;            
}
 
getNumber()
.then(
    function(data){
        console.log('resolved');
        console.log(data);
    }, 
    function(reason, data){
        console.log('rejected');
        console.log(reason);
    }
);

我们在 getNumber 函数中调用 Promise 中的 reject 来将某种失败的状态传递出来,然后在 then 中传递了两个参数。 then 方法可以接受两个参数,第一个对应 resolve 的回调,第二个对应 reject 的回调。所以在这两个回调函数中,我们可以获取对应的成功或失败返回的数据。

我们常用的 Promise 对象除了 then 方法外,还有一个 catch 方法。该方法是用来干啥的呢?then 里的参数是可选的, catch(failureCallback)then(null, failureCallback) 的缩略形式。catch 的作用其实和 then 的第二个参数一样,用来指定 reject 的回调:

getNumber()
.then(function(data){
    console.log('resolved');
    console.log(data);
})
.catch(function(reason){
    console.log('rejected');
    console.log(reason);
});

catch 除了可以用来表示 reject 的回调外,它还有另外一个作用:在执行 resolve 的回调(也就是上面代码 then 中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死 Js, 而是会进到这个 catch 方法中。

getNumber()
.then(function(data){
    console.log('resolved');
    console.log(data);
    console.log(somedata); //此处的somedata未定义
})
.catch(function(reason){
    console.log('rejected');
    console.log(reason);
});

Promise.all()

Promise 的 all 方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调。

Promise
.all([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){
    console.log(results);
});

用 Promise.all 来执行,all 接收一个数组参数,里面的值最后都会返回 Promise 对象。这样,三个异步操作并行执行,等到它们都执行完后才会进到 then 里面。

Promise.race()

race 意为 “竞争”。 all 方法的效果实际上是谁跑的慢,以谁为准执行回调,进而达到一个并行的效果。race 的效果则和 all 的效果相反,谁跑的最快就先回调执行谁。

Promise
.race([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){
    console.log(results);
});

用 Promise.race 来执行,race 接收一个数组参数,数组里面的值都是可以最终执行返回 Promise 对象。调用执行的时候都是并行执行的,一旦数组参数中的某个对象执行完并返回 Promise 对象就会立即进入 then 方法继续下一步。而后面数组其他的对象执行完依次重复该过程,体现了一个竞速的效果。

参考理解

Async/await

async/await是 es7 提出的异步特性

async字面意思是“异步”,用于声明一个函数是异步的。await的字面意思是“等待”,用来等待异步函数完成。

通常来说async/await都是跟随 Promise一起使用的。因为async返回的是一个 Promise对象。

/**
 * 成功执行
 */
async function funcSuccess() {
    const count = 10;
    return count;
}

funcSuccess().then(
    (res) => {
        console.log(res);
    }
);

/**
 * 失败执行
 */
async function funcFail() {
    const count = 10 + i;
    console.log('测试是否继续执行');
    return count;
}

funcFail()
.then(
    (res) => {
        console.log('执行成功', res);
    }
)
.catch(
    (error) => {
        console.log('执行失败', error);
    }
);

如果 async 函数执行顺利并结束,返回的 Promise 对象的状态会从等待状态转变成功状态,并输出 return 命令返回的结果(没有则为 undefined)。如果 async 函数执行途中失败,JS 会认为 async 函数已经完成执行,返回的 Promise 对象的状态会从等待转变成失败,并输出错误信息。

await只能用在async函数里面,存在于 async 内部的普通函数也不行。

/**
 * await 只能用在 async 函数内部,存在于 async 函数内部的普通函数也不行
 * 下面这段代码会直接报错
 */
async function testA() {
  function abc () {
      const ab = await new Promise(resolve => {
        setTimeout(() => {
          resolve(10);
        }, 2000);
      }); 
  }
}

引擎会统一将 await 后面的跟随值视为一个 Promise, 对于不是 Promise 对象的值会调用 Promise.resolve() 进行转化。即便此值为一个 Error 实例,经过转化后,引擎依然视其为一个成功的 Promise, 其数据为 Error 的实例。

当函数执行到 await 命令时,会暂停执行并等待其后的 Promise 结束。如果该 Promise 对象最终成功,则会返回成功的返回值,相当于将 await xxx 替换成 返回值。如果该 Promise 对象最终失败,且错误没有被捕获,引擎会直接停止执行 async 函数并将其返回对象的状态更改为失败,输出错误信息。

async 函数中的 return x 表达式,相当于 return await x 的简写。

/**
 * await 后面执行成功
 */
async function awaitFuncSuccess() {
    const n1 = await 10;
    const n2 = await new Promise<number>((resolve) => {
        setTimeout(() => {
            resolve(20);
        }, 2000);
    });
    console.log('n1', n1, 'n2', n2);
    return n1 * n2;
}

awaitFuncSuccess()
.then(
    (res) => {
        console.log('执行成功', res); // 约两秒后 输出 200
    }
)
.catch(
    (error) => {
        console.log('error', error);
    }
);

/**
 * await 后面执行失败
 */
async function awaitFuncFail() {
    const n1 = await 10;
    console.log('n1', n1);
    const n2 = await new Promise<number>((resolve, reject) => {
        setTimeout(() => {
            reject(20);
        }, 2000);
    });
    console.log('n1', n1, 'n2', n2);
    return n1 * n2;
}

awaitFuncFail()
.then(
    (res) => {
        console.log('执行成功', res);
    }
)
.catch(
    (error) => {
        console.log('error', error); // 约两秒后 输出 20
    }
);

顺序发生

/**
 * 顺序执行
 */
async function queueFunc_A() {
    const n1 = await createPromise();
    console.log('n1', n1);
    const n2 = await createPromise();
    console.log('n2', n2);
    const n3 = await createPromise();
    console.log('n3', n3);
}

function createPromise() {
    return new Promise((resolve) => {
        setTimeout(() => {
            setTimeout(() => {
                resolve(10);
            });
        }, 2000);
    });
}

async function queueFunc_B() {
    for (let i = 0; i < 3; i ++) {
        let n = await createPromise();
        console.log('N' + (i + 1), n);
    }
}

// 运行下面两个函数,都是间隔两秒依次输出
queueFunc_A();
queueFunc_B();

并发执行

仔细查看下面的代码是如何使用数组进行并行执行的

/**
 * 并行执行
 */
async function parallelFunc_A() {
    const res = await Promise.all([createPromise(), createPromise(), createPromise()]);
    console.log('Data', res);
}

async function parallelFunc_B() {
    let res = [];
    const reqs = [createPromise(), createPromise(), createPromise()];
    for (let i = 0; i < reqs.length; i++) {
        res[i] = await reqs[i];
    }
    console.log('Data', res);
}

async function parallelFunc_C() {
    let res = [];
    let reqs = [1,2,3].map(
        async (item) => {
            let n = await createPromise();
            return n + 1;
        }
    );
    for (let i = 0; i < reqs.length; i++) {
        res[i] = await reqs[i]
    }
    console.log('Data', res);
}

错误处理

一旦 await 后面的 Promise 转变成 rejected, 整个 async 函数便会终止。然而很多时候我们不希望因为某个异步操作的失败,就终止整个函数,因此需要进行合理错误处理。注意,这里所说的错误不包括引擎解析或执行的错误,仅仅是状态变为 rejected 的 Promise 对象

处理错误的方式有两种: 一种是对 Promise 对象进行包装,使其始终返回一个成功的 Promise 。 二是使用 try/catch 捕获错误。

/**
 * 错误处理
 */
async function errorFunc_A() {
    let n;
    n = await createPromiseByParam(true);
    return n;
}

async function errorFunc_B() {
    let n;
    try {
        n = await createPromiseByParam(false);
    } catch (e) {
        n = e;
    }
    return n;
}

function createPromiseByParam(param: boolean) {
    const p = new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('出错啦!')
        }, 1000);
    });
    return param ? p.catch((error) => { return 'catch error' + error}) : p;
}

// async 返回的是一个 Promise 对象,执行下面的函数获取结果
errorFunc_A().then(console.log);
errorFunc_B().then(console.log);

阅读参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,012评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,628评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,653评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,485评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,574评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,590评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,596评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,340评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,794评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,102评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,276评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,940评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,583评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,201评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,441评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,173评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,136评论 2 352

推荐阅读更多精彩内容