Promise的一些疑问及总结

Promise的几种状态?

Promise有三种状态:未定(pending)、接受(fulfillment)和拒绝(rejection)。并且,只能由未定状态变为接受状态,或者由未定状态变为拒绝状态。
它的一般写法是:

var promise = new Promise(function(resolve, reject) {
  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

Promise的状态何时发生改变?

上面代码中,只有在调用resolve(/reject)回调函数时,这个Promise的状态才变成接受(/拒绝)状态。也就是说,在执行这两个回调函数中的任意一个之前,这个Promise都是未定状态的。

如果两个回调函数相继出现,终态是什么?

考虑下面代码及其结果:

new Promise(function(success,error){
    console.log("before success");
    success();
    error();
    console.log("after error");
}).then(()=>console.log("success"),
        ()=>console.log("error"))

//输出 before success
//输出 after error
//输出 success
new Promise(function(success,error){
    console.log("before error");
    error();
    success();
    console.log("after success");
}).then(()=>console.log("success"),
        ()=>console.log("error"))
//输出 before error
//输出 after success
//输出 error

Promise的状态改变后还会再改变吗?

(Promise) 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变……状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。
——《ES6标准入门(第二版)》

如何指定两种状态的回调函数?

可以用then方法分别指定接受状态和拒绝状态的回调函数:

promise.then(function(value) {
  // 接受状态的回调函数
  }, function(error) {
  // 拒绝状态的回调函数
});

then方法有两个参数,第一个指定接受状态的回调函数,第二个指定拒绝状态的回调函数。因为Promise状态固定后不会再变,所以这两个回调函数有且只有一个在将来会被执行。
下面用例子说明状态确定和状态凝固的行为:

var Pro=new Promise(function(success,reject){
    success();
})

上面代码创建一个Promise

  1. 由于Promise一旦创建就会立刻调用。所以上述代码会执行success回调函数。
  2. 由于success回调函数被执行,说明这个Promise就是接受状态,且永远只能是接受状态。

由于Pro变量已经是接受状态了,所以此后,Pro调用then方法,只有第一个回调函数会被执行(它指定接受状态的回调行为):

  //一些其他代码.....
Pro.then(()=>console.log("success"),()=>console.log("Error"))
//输出:success

相似的,如果上面的构造函数中,执行reject方法,那么Pro状态就会被固定为拒绝状态,那么此后,Pro调用then方法,只有第二个回调函数会被执行(它指定拒绝状态的回调行为)。

then中的回调函数和Promise构造函数中的参数函数一样吗?

即问:

var p = new Promise(function(resolve, reject) {
  resolve(1);
});
function success(){}
p.then(success);

在上述代码中。resolvesuccess是一样的吗?
并不一样
resolve是指用一个值来完成这个promise,只有promise resolve了,它才变为接受状态,从而then方法注册的回调函数success才会被执行。换句话说,就算没有then也没有success,只要有resolve,它就可以变成接受状态。因果关系不能颠倒。
而它们的行为与resolve中的参数有关:参数是否是thenable类型参数(即可调用then的对象,这里就是指Promise对象):

  • 如果值是非thenable类型,如数值、字符串、数组等,它就会传入then中的参数回调函数success中执行。

  • 如果值是thenable类型,比如Promise对象。为了更好说明,使用下面示例代码:

    var p1 = new Promise(function (resolve, reject) {
      setTimeout(() => reject(new Error('fail')), 3000)
    });
    var p2 = new Promise(function (resolve, reject) {
      setTimeout(() => resolve(p1), 1000)
    });
    p2
    .then(result => console.log(result))
    .catch(error => console.log(error))
    

    在上面代码中,p2在1秒之后调用resolve,然而resolve中传入的是一个Promise对象p1作为参数,此时,p2的终态由p1来决定:尽管1秒后p2调用了resolve,似乎应该是接受状态,但是因为p1在3秒后状态才改变,目前它仍然是未定态,从而此时p2也是未定态。
    这很好理解,resolve没有实际作用,它只是用来标记状态改变的一个函数:

    1. 当它的参数不是Promise对象时,表明至此结束,可以进入接受状态,这个值可以传递给then中的回调函数。
    2. 当它的参数是另一个Promise对象时(记为otherPro),表明尚未结束,有赖于另一个Promise的状态,只有当另一个Promise状态确定,那么它的状态也就随之确定。此时,调用then中的回调函数时,当然不能将otherPro作为参数传入,因为在概念上就不合理,没有意义。那么这个参数是什么呢?它就是otherPro中的resolve(/reject)的参数。当然,如果otherProresolve(/reject)的参数又是一个Promise,则then的回调函数的参数就是它中的resolve(/reject)的参数,以此类推。原则上:务必保证then中回调函数的参数值不是一个thenable对象。

    从上面第二点来看:如果p2rejectresolvep1,则p1的终态决定了p2的终态;p2的回调函数的参数就是p1resolve/reject参数。所以,p2后面的then其实完全可以理解为是调用在p1上的
    这里,p1使用reject(new Error("...")),表明它成为拒绝状态并抛出错误,但是,p2后的then(已知,可以认为它是调用在p1上的)中缺少第二个参数,说明这个then对于p1无法处理,则then会返回一个新的Promise,它是拒绝状态(与p1状态一致),且它的内部属性[[PromiseValue]]会保留(引用)这个值,然后转发。在它上面又调用catch方法(catch方法是then(null,function(){/*..*/})的简写,即仅指定第二个处理拒绝状态的回调函数),它就可以处理了。

then的返回值是什么?

then是有返回值的,它返回一个新的Promise(与此前任何一个Promise都不同)。然而这个Promise的行为与then中回调函数的返回值有关:

  1. 如果then中的回调函数返回一个值,那么then返回的Promise将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。

    new Promise(function(success,reject){
      success()//确定是接受状态
    })
    .then(()=>"hello!") //then中回调函数返回一个字符串值
        //上面then返回一个新的Promise,与下面的then进行链式调用
    .then((value)=>console.log("success! "+value), //第一个参数回调函数被执行
          (value)=>console.log("Error! "+value))
    
    //输出:success! hello!
    

    在上面代码中,由于Promise一经创建立刻调用,所以在第一个then之前,该Promise已经是接受状态。所以,第一个then中的回调函数会被执行,它返回了一个值(字符串),那么then将会返回一个新的Promise(请不要混淆then的返回值和then中回调函数的返回值,代码注释已做区别),并且这个新的Promise就是接受状态。
    第二个then就是针对这个新的Promise而调用,不出所料,第一个参数回调函数被执行。并且值得注意,返回的值(字符串"hello!")也将会作为回调函数的第一个参数传入,如你所见,打印出“success! hello!”。
    【当然,如果then中的参数回调函数根本没有返回值,那么就相当于返回undefined,此时then返回的新的Promise仍然是接受状态,但是值是undefined。】

  2. 如果then中的回调函数抛出一个错误,那么then返回的Promise将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数的参数值。

    (这个与第1个的情况相似,不讨论。)

  3. 如果then中的回调函数返回一个已经是接受状态的Promise,那么then返回的Promise也会成为接受状态,并且那个Promise的接受状态的回调函数的参数值也将会作为该被返回的Promise的接受状态回调函数的参数值。

    这一条与第1条也有相似之处。我们假设已经有一个成为接受状态的Promise

    var Pro=new Promise(function(success,error){
      success("hello!");   //回调函数的参数是"hello!"
    });
    

    "hello!"就是上面所说的“那个Promise的接受状态的回调函数的参数值”,同时上面也说:“……也将会作为该被返回的Promise的接受状态回调函数的参数值”,下面的代码可以说明:

    new Promise(function(resolved,rejected){
      resolved(); //接受状态
    })
    .then(()=>Pro)  //保证会返回Pro
    .then((value)=>console.log("success "+value),  //接受状态回调函数被执行
          (value)=>console.log("error "+value));
    
    //输出:success hello!
    

    上面,第一个then的回调函数返回了已经是接受状态的Pro,所以,Pro上调用的then(第二个then)的第一个参数回调函数被执行,打印信息。同时,因为"hello!"是Pro的接受状态的回调函数的参数值,所以它也是then返回的新的Promise的接受状态的回调函数的第一个参数。

  4. 如果then中的回调函数返回一个已经是拒绝状态的Promise,那么then返回的Promise也会成为拒绝状态,并且将那个Promise的拒绝状态的回调函数的参数值作为该被返回的Promise的拒绝状态回调函数的参数值。

    (这个与第3个的情况相似,不讨论。)

  5. 如果then中的回调函数返回一个未定状态(pending)的Promise,那么then返回Promise的状态也是未定的,并且它的终态与那个Promise的终态相同;同时,它变为终态时调用的回调函数参数与那个Promise变为终态时的回调函数的参数是相同的。

    这个比前面的理解的复杂。关键是理解:“……并且它的终态与那个Promise的终态相同”。假定我们有一个Promise,它在五秒之后才会变为接受状态:

    var Pro=new Promise(function(success){
      setTimeout(()=>success("Hello"),5000); //保持5秒未定态,5秒后为接受态
    });
    

    则:

    new Promise(function(resolved){
      resolved();
      console.log("waiting for 5s...");
    })
    .then(()=>Pro)
    .then((value)=>console.log(value+" in France is bonjour"));
    
    //输出:waiting for 5s...
    //(在此等待5秒)
    //输出:Hello in France is bonjour
    

    上面先使得匿名的Promise变为接受态,然后输出提示语句(“waiting for 5s...”)。这样,第一个then的回调函数会返回Pro
    关键在于:此时Pro显然还是未定态,所以第一个then返回新的Promise也是未定态,所以第二个then中的回调函数目前不做反应。等待5秒后,Pro变成了接受态,从而then返回的新的Promise也就相应变成接受态,从而第二个then中的回调函数被激活,打印出信息,不出所料,它的参数正是Pro中接受态的回调函数的参数"Hello"。

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

推荐阅读更多精彩内容