JavaScript异步之Promise

传统的JavaScript异步通常基于回调实现,但回调方式有两个重要的缺点:

  • 不便于调试:由于回调函数是基于事件队列实现的,当回调方法条用时,其外部调用函数并不在函数执行栈中,这给debug带来了极大不便。来看下下面这个例子:

    function init(name) {
        test(name)
    }
    setTimeout(function A() {
        setTimeout(function() {
            init();
        }, 0);
    }, 0);
    
1.png
可以看到,setTimeout并未出现在异常堆栈中
  • 回调地狱:在异步编程中,通常会出现回调嵌套的场景。一层层回调相互嵌套,称为回调地狱。严重影响代码可读性

Promise

由于传统回调的诸多缺点,Promise被提出以一种更友好的方式解决上述问题。Promise是什么呢?简单来说,Promise是一个封装未来事件结果的可复用异步任务管理机制。从这个定义中,我们可以看到Promise的几个主要特点:

  • 异步:Promise是用于描述未来事件的,未来事件什么时候发生并不知晓,因而其必然是基于异步实现的
  • 任务管理:当未来事件发生后,如何处理未来事件?未来事件成功如何处理?失败又如何处理?所以Promise还涉及到任务管理
  • 可复用:一个未来事件可能有多个回调处理,同时异步任务也可能是多重嵌套的,即异步任务回调中还嵌套着另一个异步任务。所以Promise必须是可复用的

术语

首先来看下Promise下几个常用术语:

  • Promise:指一个拥有符合规范的then方法的对象
  • thenable:指一个定义了then方法的对象
  • resolve:改变一个promise对象从等待状态到已完成或拒绝状态,一旦改变,不可再改
  • reject reason:拒绝原因

另外,一个Promise中还有三个状态:

  • pending:等待、初始状态
  • fullfilled:已完成,未来事件操作成功
  • rejected:已拒绝,未来事件操作失败

一个Promise对象的状态变化只能有如下两种:

pending ----->   fullfilled

pending ------>  rejected

THEN方法

Promise所提供的,用于访问未来事件处理结果的方法:

Promise.then(onFulfilled, onRejected)
/*
* - 两个参数均为可选,均有默认值,若不传入,则会使用默认值;
* - 两个参数必须是函数,否则会被忽略,使用默认函数;
* - onFulfilled: 在promise已完成后调用且仅调用一次该方法,该方法接受promise最终值作参数;
* - onRejected: 在promise被拒绝后调用且仅调用一次该方法,该方法接受promise拒绝原因作参数;
* - 两个函数都是异步事件的回调,符合JavaScript事件循环处理流程
*/

Resolution

Promise的核心就是一个resolution的过程,即处理未来事件,并确定事件成功或失败的条件,并在对应条件下执行onFullfilledonRejected(由then方法传入)方法通知调用方。

接下来看一个Promise的例子:

let myPromise = new Promise((resolve, reject) =>{
    setTimeout(function(){
        console.log('resolve');
        resolve('success')
    }, 1000 * 3)
});

myPromise.then((msg) => {
    console.log("Yay!" + msg);
});

console.log("after execute promise");

对应的输出为:

after execute promise
resolve
Yay!success

从输出可以看出其异步特性:Promise的实例化以及then方法都不是阻塞式函数,javascript依然继续向下执行,所以最先输出的便是after execute promise

(resolve, reject) =>{
    setTimeout(function(){
        console.log('resolve');
        resolve('success')
    }, 1000 * 3)
}

初始化Promise对象时传入的处理函数是Promise的核心,如上述代码所示,在该Promise对象中设定一个3S的定时器。3S秒后,任务执行成功,所以通过调用resolve将成功信息透出,同时resolve方法又会通过onResolved方法(即在then方法中传入的处理函数)将该信息透出给调用者。至此,一个完整的Promise流程执行完毕。其中resolve reject方法由Promise提供,用户执行指定何时调用该方法即可。

接下来再来看一个例子:

let myPromise = new Promise((resolve, reject) =>{
    setTimeout(function(){
        console.log('resolve');
        resolve('success')
        reject('failed')
    }, 1000 * 3)
});

myPromise.then((msg) => {
    console.log(msg)
    console.log("Yay!" + msg);
    return 'first promise'
}, (reason) => {
    console.log('rejected:' + reason)
});

对应的输出为:

resolve
success
Yay!success

可以看到,尽管同时调用了resolvereject,但只有resolve被执行了,这也再次验证了Promise的状态不可变性:即Promise的状态一旦变为resolvedrejected便不会再改变。

接下来再改变下上述代码:

let myPromise = new Promise((resolve, reject) =>{
    setTimeout(function(){
        console.log('resolve');
        resolve('success')
        resolve('success')
    }, 1000 * 3)
});

myPromise.then((msg) => {
    console.log(msg)
    console.log("Yay!" + msg);
    return 'first promise'
}, (reason) => {
    console.log('rejected:' + reason)
});
resolve
success
Yay!success

可以看到,尽管调用了两次resolve方法,但onResolve方法只执行了一次,即当promise对象的状态一旦变为resolved或是rejected后,便不再执行resolvereject方法。

看完了上述的例子,我们重新来看下resolvereject方法:

Promise.resolve(x)

/*resolve方法返回一个已决议的Promsie对象:

若x是一个promise或thenable对象,则返回的promise对象状态同x;
若x不是对象或函数,则返回的promise对象以该值为完成最终值;
否则,详细过程依然按前文Promsies/A+规范中提到的规则进行。*/

Promsie.reject(reason)

/*返回一个使用传入的原因拒绝的Promise对象。*/

Promise.prototype.then

看完了resolve方法和reject方法,接下来来看下then方法:

该方法为promsie添加完成或拒绝处理器,将返回一个新的promise,该新promise接受传入的处理器调用后的返回值进行决议;若promise未被处理,如传入的处理器不是函数,则新promise维持原来promise的状态。

来看下下面这个例子:

var promise = new Promise((resolve, reject) => {
    setTimeout(function() {
        resolve('success');
    }, 10);
});
promise.then((msg) => {
  console.log('first messaeg: ' + msg);
}).then((msg) => {
    console.log('second messaeg: ' + msg);
});

其输出结果为:

first messaeg: success
second messaeg: undefined

可以看到,第一个then方法成功接收到了resolve方法返回的结果,但第二个then方法接收到的却是undefined。这是为什么呢?then方法会返回一个promise对象,并且该新promise根据其传入的回调执行的返回值,进行决议,而函数未明确return返回值时,默认返回的是undefined,这也是上面实例第二个then方法的回调接收undefined参数的原因。

所以接下来我们队上次上述代码进行修改:

var promise = new Promise((resolve, reject) => {
    setTimeout(function() {
        resolve('success');
    }, 10);
});
promise.then((msg) => {
  console.log('first messaeg: ' + msg);
  return 'succss';
}).then((msg) => {
    console.log('second messaeg: ' + msg);
});

对应的输出就变为:

first messaeg: success
second messaeg: success

Promise.prototype.catch

catch方法等同于then方法中的onRejected方法,为promise对象添加异常处理逻辑。

链式调用

正式由于then方法返回一个promise对象,所以可以基于Promise实现链式回调调用:

var fourthPromise = new Promise((resolve, reject) => {
    setTimeout(()=>{
        console.log('first promise')
        resolve('first success');
    },1000);
}).then((msg) => {
    console.log(msg)
    return new Promise((resolve, reject) => {
        console.log('second promise')
        setTimeout(() => {resolve('second success')}, 1000)
    })
}).then((msg) => {
    console.log(msg)
    return new Promise((resolve, reject) => {
        console.log('third promise')
        setTimeout(() => {resolve('third success')}, 1000)
    })
});

fourthPromise.then((msg) => {
    console.log(msg)
})

对应的输出为:

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

推荐阅读更多精彩内容