关于promise

参考:阮一峰 promise

1.promise含义
promise是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合理和强大。

所谓promise,简单说就是一个容器,里面保存着某个未来才会结束的事件的结果。从语法上说,promise是一个对象,从他可以获得异步操作的消息。promise提供统一的API,各种异步操作都可以用同样的方法进行处理。

promise对象有以下两个特点:
(1)对象的状态不受外界影响。promise对象有三种状态:pending、fulfilled、rejected。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是promise这个名字的由来,“承诺”,其他手段无法改变状态。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。promise对象的状态改变只有两种情况:从pending变成fulfilled,从pending变成rejected。只要这两种情况发生,状态就凝固不会再变了,这时就称为resolved。如果改变已经发生,再对promise对象添加回调函数,也会立即得到这个结果。这与事件完全不同。事件的特点是,如果你错过了它,再去监听是得不到结果的。

有了promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调。此外,promise对象提供统一的接口,使得控制异步操作更加容易。

promise也有一些缺点。首先,无法取消promise,一旦新建就会立即执行,无法中途取消。其次,如果不设置回调函数,promise内部抛出的错误,不会反应到外部。第三,当处于pending状态时,无法得知目前进展到哪一个阶段。

如果某些事件不断的反复发生,使用Stream模式比部署promise更好。???stream是啥???

2.基本用法
promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由JS引擎提供。
resolve函数的作用是,将promise对象的状态从pending变为fulfilled,在异步操作成功时调用,并将异步操作的结果,作为参数传递出去。
reject函数的作用是,将Promise对象的状态从pending变为rejected,在异步操作失败时调用,并将异步操作报出的错误作为参数传递出去。
promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。promise.then(function(val){}, function(err){})

let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});
promise.then(function() {
  console.log('resolved.');
});
console.log('Hi!');
// Promise
// Hi!
// resolved

上面代码中Promise 新建后立即执行,所以首先输出的是Promise。然后,then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以resolved最后输出。

如果调用resolve函数和reject函数时带有参数,那么它们的参数会被传递给回调函数。reject函数的参数通常是Error对象的实例,表示抛出的错误;resolve函数的参数除了正常的值以外,还可能是另一个 Promise 实例,比如像下面这样。

const p1 = new Promise(function (resolve, reject) {
  // ...
});
const p2 = new Promise(function (resolve, reject) {
  // ...
  resolve(p1);
})

p2的resolve方法将p1作为参数,即一个异步操作的结果是返回另一个异步操作。
注意,这时p1的状态就会传递给p2,也就是说,p2自己的状态无效,由p1的状态决定p2的状态,p2后面的then语句都变成针对p1的。如果p1的状态是pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是resolved或者rejected,那么p2的回调函数将会立刻执行。
注意,调用resolve或reject并不会终结 Promise 的参数函数的执行。写在resolve或reject之后的语句是会执行的。但是一般来说,调用resolve或reject以后,Promise 的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolve或reject的后面。所以,最好在它们前面加上return语句,这样就不会有意外。

new Promise((resolve, reject) => {
  resolve(1);
  console.log(2);
}).then(r => {
  console.log(r);
});
// 2
// 1

new Promise((resolve, reject) => {
  return resolve(1);
  // 后面的语句不会执行
  console.log(2);
})

3.promise的API
(1) Promise.prototype.then
如果then方法返回的是一个新的promise实例,那可以采用链式写法,then方法后再调用一个then方法。

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

getJSON('/posts.json').then(function(posts) {
  // ...
}).catch(function(error) {
  // 处理 getJSON 和 then方法中回调函数运行时发生的错误
  console.log('发生错误!', error);
});

一般来说,不要在then()方法里面定义 Reject 状态的回调函数(即then的第二个参数),总是使用catch方法。
跟传统的try/catch代码块不同的是,如果没有使用catch()方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。Promise 内部的错误不会影响到 Promise 外部的代码,通俗的说法就是“Promise 会吃掉错误”。

(3)Promise.prototype.finally
finally方法用于指定不管promise对象最后状态如何,都会执行的操作。finally的回调函数不接受任何参数,里面的操作应该是与状态无关的。

(4)Promise.all()
Promise.all()用于将多个Promise实例,包装成一个新的Promise实例。
const p = Promise.all([p1, p2, p3]);它接受一个数组[p1,p2,p3]作为参数,数组内都是promise实例,如果不是就会先调用promise.resolve方法将其转为promise实例,再进一步处理。另外,Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。

p的状态由p1、p2、p3决定,分成两种情况。

  • 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
  • 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

(5)Promise.race()
Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.race([p1, p2, p3]);只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
下面是一个例子,如果指定时间内没有获得结果,就将 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);

上面代码中,如果 5 秒之内fetch方法无法返回结果,变量p的状态就会变为rejected,从而触发catch方法指定的回调函数。

(6) Promise.allSettled()
Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。
有时候,我们不关心异步操作的结果,只关心这些操作有没有结束。这时,Promise.allSettled()方法就很有用。

(7)Promise.any() 还是提案
只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。该方法目前是一个第三阶段的提案
Promise.any()跟Promise.race()方法很像,只有一点不同,就是不会因为某个 Promise 变成rejected状态而结束。

(8)Promise.resolve()
将现有对象转为promise对象。
const jsPromise = Promise.resolve($.ajax('/whatever.json'));将 jQuery 生成的deferred对象,转为一个新的 Promise 对象。

Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))

Promise.resolve方法的参数分成四种情况。

  • 如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
  • 参数是一个thenable对象(具有then方法的对象),会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。
  • 参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved。
  • 不带参数,直接返回一个resolved状态的 Promise 对象。

需要注意的是,立即resolve()的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,而不是在下一轮“事件循环”的开始时。

setTimeout(function () { //在下一轮“事件循环”开始时执行
  console.log('three');
}, 0);
Promise.resolve().then(function () {  //在本轮“事件循环”结束时执行
  console.log('two');
});
console.log('one');
// one
// two
// three

(9) Promise.reject()
返回一个新的 Promise 实例,该实例的状态为rejected。
注意,Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。这一点与Promise.resolve方法不一致。

(10)Promise.try() ---还是提案
让同步函数同步执行,异步函数异步执行,并且让它们具有统一的 API .

const f = () => console.log('now');
Promise.try(f);
console.log('next');
// now
// next

4.promise的应用
(1)加载图片
将图片的加载写成一个Promise,一旦加载完成,Promise的状态就发生变化。

(2) generator函数与promise的结合
使用 Generator 函数管理流程,遇到异步操作的时候,通常返回一个Promise对象。

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