Javascript异步编程之Promise

异步回调的问题

  • 层次嵌套很深,难以维护
  • 无法正常的使用return和throw
  • 无法正常的检索堆栈信息 (每次回调执行都是在系统层面上新的堆栈)
  • 多个回调之间难以建立联系

Promise的设计

new Promise(
    /* 执行器 executor */
    function (resolve, reject) {
        /* 一段耗时很长的异步操作 */
    resolve(); // 数据处理完成
    reject(); // 数据处理出错
    }
)
.then(function A() {
   // 成功,下一步
 }, function B() {
   // 失败,做相应处理
});
  • Promise是一个代理对象,它和原先要进行的异步操作并无关系,只是把这段耗时很长的异步操作放在执行器中;
  • 通过引入一个回调,避免更多的回调;
  • Promise有3个状态:
    -pending [待定] 初始状态
    -fulfilled [实现] 操作成功
    -rejected [被否决] 操作失败
  • 当Promise状态一旦发生改变,就会触发对应.then里的响应函数来处理后续步骤;
  • Promise状态一经改变,就不会再变;
  • Promise实例一经创建,执行器立即执行
    Promise.png
  1. 定时执行
console.log('here we go');
new Promise( resolve => {
    setTimeout( () => {
        resolve('hello');
    }, 2000);
})
.then( value => {
    console.log( value + ' world');
});

输出结果:
here we go
Promise {<pending>}
(约2s后)hello world

  1. 分两次,依次定时执行
console.log('here we go');
new Promise( resolve => {
    setTimeout( () => {
        resolve('hello');
    }, 2000);
})
.then( value => {
    console.log(value);
    return new Promise( resolve => {
        setTimeout( () => {
            resolve('world');
         }, 2000);
    });
})
.then( value => {
      console.log( value + ' world');
});

输出结果:
here we go
Promise {<pending>}
(约2s后)hello
(约2s后)world world

  1. 对已完成的Promise执行.then
console.log('start');

let promise = new Promise(resolve => {
    setTimeout(() => {
        console.log('the promise fulfilled');
        resolve('hello, world');
    }, 1000);
});

setTimeout(() => {
    promise.then( value => {
        console.log(value);
    });
}, 3000);

输出结果:
start
74
(约1s后)the promise fulfilled
(约2s后)hello, world

在任何地方生成一个Promise队列后,可以把它作为一个变量传到其他地方,不管这个Promise状态是否是完成状态,队列都会依次执行;若为完成,会依次执行Promise内的执行器部分,若已完成,后面追加的.then将会得到Promise返回的值

  1. then()里不返回Promise
console.log('here we go');
new Promise(resolve => {
    setTimeout( () => {
        resolve('hello');
    }, 2000);
})
.then( value => {
     console.log(value);
     console.log('everyone');
     (function () {
         return new Promise(resolve => {
            setTimeout(() => {
                console.log('Mr.Laurence');
                resolve('Merry Xmas');
             }, 2000);
         });
    }());
    return false;
})
.then( value => {
     console.log(value + ' world');
});

输出结果:
here we go
Promise {<pending>}
hello
everyone
false world
(约2s后)Mr.Laurence

在这里,第一个then()里相应函数返回的Promise并不会等待新创建的Promise实例执行(那段立即函数,陷阱!),即使then()响应函数返回值是false;也就是说,在Promise里即使没有直接返回一个Promise实例,也会默认去执行下一个环节。这里将return false注释掉,可以发现,会默认返回undefined值,并传递给下一个then()的相应函数中。

下面介绍一下.then()

  • 接收两个函数作为参数,分别代表fulfilledrejected
  • .then()返回一个新的Promise实例,所以它可以链式调用
  • 当前面的Promise状态改变时,.then()会根据其最终状态选择相应的响应函数进行处理
  • 状态响应函数可以返回一个新的Promise实例,或其它值,不返回则为undefined
  • 如果返回新的Promise实例,那么下一级.then()会在新Promise状态发生改变后执行
  • 如果返回其它值,则会立刻执行下一级.then()

避免嵌套.then()

因为.then()返回的还是Promise实例,会等里面的.then()执行完,才执行外面的。对于我们来说,此时最好将其展开,避免then()函数里嵌套then(),也方便阅读。

四道题目

原问题地址译文地址请戳←
假设doSomething和doSomethingElse返回的都是一个Promise实例,下面的四种 promises 的区别是什么?

  1. 问题一
doSomething()
    .then(function () {
        return doSomethingElse();
    })
    .then(finalHandler);

答案:
(最常见的Promise形式,then()相应函数返回一个新的Promise实例)

// doSomething
// |-----------|
//             doSomethingElse(undefined)
//             |------------|
//                          finalHandler(resultOfDoSomethingElse)
//                          |------------|
  1. 问题二
doSomething()
    .then(function () {
        doSomethingElse();
    })
    .then(finalHandler);

答案:
(第一个then()响应函数没有直接返回一个新的Promise实例,也就是说doSomethingElse()返回的Promise实例并没有返回给then()的响应函数,此时默认返回undefined,并且finalHandler会立刻执行几乎会和doSomethingElse ()同时)

// doSomething
// |------------------|
//                    doSomethingElse(undefined)
//                    |------------------|
//                    finalHandler(undefined)
//                    |------------------|
  1. 问题三
doSomething()
    .then(doSomethingElse())
    .then(finalHandler);

答案:
(第一个then()函数中传入的是一个函数,且采用的是函数执行的方式;实际上传入的是一个Promise实例。这种情况下,doSomethingElsedoSomething的执行可以看作几乎是同时的,因为是在同一个栈中执行的)。

在Promise的规范中,then()中传入的若不是一个函数,这个then()就会被忽略;在这里doSomethingElse执行完返回的是一个Promise。也就是说,finalHandler监听的是doSomething的完成时间,在doSomething完成后,finalHandler就会被执行。

// doSomething
// |------------------|
// doSomethingElse(undefined)
// |----------------------------------|
//                    finalHandler(resultOfDoSomething)
//                    |------------------|
  1. 问题四
doSomething()
    .then(doSomethingElse)
    .then(finalHandler);

答案:
then()接收两个函数作为参数,第一个是fulfilled状态,第二个是rejected状态);在这里doSomethingElse作为fulfilled状态的响应函数对其进行后续处理

// doSomething
// |-----------|
//             doSomethingElse(resultOfDoSomething)
//             |------------|
//                         finalHandler(resultOfDoSomethingElse)
//                         |------------------|

关于错误处理

Promise会自动捕获内部异常(即在执行器当中如果发生错误或自己抛出错误,Promise的状态就会被改为rejected),随后调用rejected响应函数进行处理,也会向后面寻找catch()响应函数进行处理。

  1. catch捕获
console.log('here we go');
new Promise( resolve => {
    setTimeout( () => {
        throw new Error('bye');
    }, 2000);
})
    .then( value => {
        console.log( value + ' world');
    })
    .catch( error => {
        console.log( 'Error:', error.message);
    });

输出结果:(fulfilled响应函数不会得到执行)
here we go
Promise {<pending>}
(约2s后)Uncaught Error: bye
at setTimeout (<anonymous>:4:15)

  1. reject响应捕获
console.log('here we go');
new Promise( (resolve, reject) => {
    setTimeout( () => {
        reject('bye');//throw new Error('bye');
    }, 2000);
})
    .then( value => {
        console.log( value + ' world');
    }, value => {
        console.log( 'Error:', value);
    });

推荐使用第一种,更加清晰好读,并且可以捕获前面所有.then()的错误。

关于catch()

实际上只是 then(null, ...)的语法糖

console.log('here we go');

new Promise(resolve => {
    setTimeout(() => {
        resolve();
    }, 1000);
})
    .then( () => {
        console.log('start');
        throw new Error('test error');
    })
    .catch( err => {
        console.log('I catch:', err);

        // 下面这一行的注释将引发不同的走向
        // throw new Error('another error');
    })
    .then( () => {
        console.log('arrive here');
    })
    .then( () => {
        console.log('... and here');
    })
    .catch( err => {
        console.log('No, I catch:', err);
    });

输出结果:
here we go
Promise {<pending>}
(约1s后)start
I catch: Error: test error
at Promise.then (<anonymous>:10:15)
at <anonymous>
arrive here
and here

可以看出,catch()也返回一个Promise实例,并且catch()内部没有抛出错误时,返回的这个Promise实例是fulfilled状态,所以接下来的then()都会被执行。

注意:

建议在所有队列最后都加上catch(),以避免漏掉错误处理造成意想不到的问题。

References

[翻译] We have a problem with promises

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

推荐阅读更多精彩内容

  • Promise 对象 Promise 的含义 Promise 是异步编程的一种解决方案,比传统的解决方案——回调函...
    neromous阅读 8,703评论 1 56
  • Promise的含义:   Promise是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和...
    呼呼哥阅读 2,167评论 0 16
  • 你不知道JS:异步 第三章:Promises 在第二章,我们指出了采用回调来表达异步和管理并发时的两种主要不足:缺...
    purple_force阅读 2,058评论 0 4
  • 目录:Promise 的含义基本用法Promise.prototype.then()Promise.prototy...
    BluesCurry阅读 1,490评论 0 8
  • 宝宝终于好多了!!!今天妈妈发来她坐别人车车的视频,精神好了许多,我转发给她爸爸,她爸爸说又蹭人家车…可是没有...
    jr812阅读 81评论 0 0