阮一峰ES6教程读书笔记(十二)Promise对象

Promise对象

1. Promise概述

Promise是异步编程的一种解决方案,它比传统的额解决方案--回调函数和事件更合理和强大。

所谓Promise可以简单理解为一个容器,里面保存未来才能有结果的事件,比如客户端发出的请求,但是它会在事件成功或者失败后改变状态,并且也只会因为里面保存的事件的结果而改变状态,状态改变后就凝固了,即不能再改变为其他状态了。

Promise对象有三种状态,分别是pendingfulfilled(已成功)和rejected(已失败),但确切来说,一个Promise对象只会有两种状态,即pending(进行中)和resolved(已凝固)因为它的状态一旦改变就会凝固,再也无法改变。

2. 基本用法

ES6 提供Promise对象的构造函数,使我们可以很方便的使用Promise对象。

const p = new Promise((resolve, reject) => {
    if (/* 异步操作成功 */){
        resolve(value);
    } else {
        reject(error);
    }
})

Promise对象一旦生成后就会立即执行,然后根据执行结果改变状态,我们可以使用Promise的实例方法then来获取事件结果。

p.then(res => {
    console.log(res)
}, err => {
    console.log(err)
})

then方法接受两个回调函数作为参数,第一个回调是fulfilled时调用的函数,第二个回调则是rejected时调用的函数。
下面这个例子会帮我们进一步了解Promise对象的特性。

const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('hello')
    }, 0)
    reject('bye')
})

promise.then(res => {
    console.log(`${res} world`)
}, err => {
    console.log(`${err} world`)
})

// bye world

上面的代码中,我们在Promise对象中写了分别写了resolvereject,但是因为resolve使用了setTimeout方法,会让resolve方法在主线程执行完毕后才会执行,所以是reject先执行,这时,Promise对象就会变成rejected,并返回结果。

下面是一个用Promise实现ajax操作的例子:

const getJSON = function(url) {
  const promise = new Promise(function(resolve, reject){
    const handler = function() {
      if (this.readyState !== 4) {
        return;
      }
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };
    const client = new XMLHttpRequest();
    client.open("GET", url);
    client.onreadystatechange = handler;
    client.responseType = "json";
    client.setRequestHeader("Accept", "application/json");
    client.send();

  });

  return promise;
};

getJSON("/posts.json").then(function(json) {
  console.log('Contents: ' + json);
}, function(error) {
  console.error('出错了', error);
});

上面代码中,getJSON是对 XMLHttpRequest 对象的封装,用于发出一个针对JSON 数据的 HTTP 请求,并且返回一个Promise对象。需要注意的是,在getJSON内部,resolve函数和reject函数调用时,都带有参数。

另外,还有一个比较重要的点,就是resolve以及reject的作用并不等同于return,尽管调用了上述的两个方法,接下来的语句仍然会执行。

const p = new Promise((resolve, reject) => {
    resolve('world')
    console.log('hello')
    return 'promise'
})

p.then(res => {
    console.log(res)
})

// hello
// world

通过上面的代码我们可以看到即使我们调用resolve方法,但是下面的语句仍然执行了,并且我们也可以得知,在Promise中使用return不能达到我们预期的效果,因为then只接受resolvereject的参数。

3. Promise.prototype.then()

then方法是定义在Promise的原型上的,所以每一个Promise实例都可以调用这个方法,then方法接受两个函数作为参数。

const promise = new Promise((re, rj) => {
    rj(new Error('fail in promise'))
})

promise.then(null, err => {
    console.log(err)
    throw(new Error('fail in another promise'))
}).then(null, err => {
    console.log(err)
})

// Error: fail in promise
// Error: fail in another promise

通过上面的代码我们可以看出,then方法返回的仍然是一个Promise实例,所以我们仍然可以后面调用then方法。

4. Promise.prototype.catch()

Promise.prototype.catch方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。

// 写法一
const promise = new Promise(function(resolve, reject) {
  try {
    throw new Error('test');
  } catch(e) {
    reject(e);
  }
});
promise.catch(function(error) {
  console.log(error);
});

// 写法二
const promise = new Promise(function(resolve, reject) {
  reject(new Error('test'));
});
promise.catch(function(error) {
  console.log(error);
});

通过上面的代码我们可以看出catch方法既可以作为then方法的第二个参数,来处理Promiserejected状态,同时也可以捕获Promise内部的错误,所以阮一峰大神在书中写道,我们更推荐使用catch方法来替代then方法的第二个参数,因为这样可以使得Promise变成一个try...catch结构,可以防止Promise内部错误冒泡出去。

const someAsyncThing = function() {
  return new Promise(function(resolve, reject) {
    // 下面一行会报错,因为x没有声明
    resolve(x + 2);
  });
};

someAsyncThing().then(function() {
  console.log('everything is great');
});

setTimeout(() => { console.log(123) }, 2000);
// Uncaught (in promise) ReferenceError: x is not defined
// 123

一般总是建议,Promise 对象后面要跟catch方法,这样可以处理Promise 内部发生的错误。catch方法返回的还是一个 Promise 对象,因此后面还可以接着调用then方法。

5. Promise.prototype.finally()

书中讲到,finally其实是一个then的特里,因为如果我们调用then方法,会根据Promise的状态来判断到底执行哪一个回调,但是finally是无论怎样都会执行,所以说,finally并不依赖于Promise的状态,我们可以简单理解为:

promise
.finally(() => {
  // 语句
});

// 等同于
promise
.then(
  result => {
    // 语句
    return result;
  },
  error => {
    // 语句
    throw error;
  }
);

它的实现也很简单:

Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
};

6. 其他的实例方法

这里的方法我只记录了说明,如果读者朋友有兴趣,请参阅原著。

6.1 Promise.all()

const p = Promise.all([p1, p2, p3]);

all(),该方法用于将多个Promise实例包装成一个新的Promise实例,all()方法接受一个数组或者具有Iterator接口的数据结构作为参数,该数组内的元素为Promise对象,包装后的Promise对象的状态由成员决定:

  • 如果所有的成员都为fulfilled,那么该对象的状态变成fulfilled,并且将成员的返回值按顺序组成一个数组传给回调参数。
  • 如果成员中有一个为rejected,那么该对象就会变成rejected,并且将第一个被rejected的成员的返回值传递给回调参数。

6.2 Promise.race()

const p = Promise.race([p1, p2, p3]);

race()方法同样是将多个Promise实例包装为一个新的Promise实例,如果有成员不是Promise,那么就会用Promise.resolve()方法将其转为Promise实例,该方法的作用是取成员中最快改变状态的的那一个成员作为该对象的状态,并且将返回值作为参数传递给回调函数。
下面是一个例子,如果指定时间内没有获得结果,就将Promise 的状态变为reject,否则变为resolve

const p = Promise.race([
  fetch('/resource-that-may-take-a-while'),
  new Promise(function (resolve, reject) {
    setTimeout(() => reject(new Error('request timeout')), 5000)
  })
]);

p
.then(console.log())
.catch(console.error);

6.3 Promise.allSettled()

Promise.allSettled()方法接受一组Promise实例作为参数,包装成一个新的 Promise实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。该方法由 ES2020 引入。
该方法返回的新的Promise 实例,一旦结束,状态总是fulfilled,不会变成rejected。状态变成fulfilled后,Promise 的监听函数接收到的参数是一个数组,每个成员对应一个传入Promise.allSettled()Promise 实例。

const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);

const allSettledPromise = Promise.allSettled([resolved, rejected]);

allSettledPromise.then(function (results) {
  console.log(results);
});
// [
//    { status: 'fulfilled', value: 42 },
//    { status: 'rejected', reason: -1 }
// ]

上面代码中,Promise.allSettled()的返回值allSettledPromise,状态只可能变成fulfilled。它的监听函数接收到的参数是数组results。该数组的每个成员都是一个对象,对应传入Promise.allSettled()的两个Promise 实例。每个对象都有status属性,该属性的值只可能是字符串fulfilled或字符串rejectedfulfilled时,对象有value属性,rejected时有reason属性,对应两种状态的返回值。

6.4 Promise.any()

Promise.any()方法接受一组Promise 实例作为参数,包装成一个新的 Promise 实例。只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。

6.5 Promise.resolve()

resolve的作用就是将现有对象转为Promise对象,该方法如果接受的参数不是一个对象的会,就会返回一个新的Promise对象,状态为resolved

6.6 Promise.reject()

Promise.reject(reason)方法也会返回一个新的Promise 实例,该实例的状态为rejected

const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))

p.then(null, function (s) {
  console.log(s)
});
// 出错了

参考链接

作者:阮一峰

链接:http://es6.ruanyifeng.com/#docs/promise

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

推荐阅读更多精彩内容