Promise 使用详解

查看了阮一峰ES6 借鉴里面的内容总结

什么是 Promise

Promise 是异步编程的一种解决方案, 没有 Promise 之前, 使用的是 回调函数和事件

回调地狱

  • 以前没有 Promise 时, 我们使用回调来解决异步问题, 但一旦回调的次数变多, 回调里面嵌套回调, 代码就变得难以维护
// 当一个数据返回, 使用结果来请求另一个数据, 等待结果再次请求其它数据... , 无穷尽也
<script>
$.ajax({
  type: 'get',
  url: './user.json',
  success: (res) => {
    const { id } = res
    
    $.ajax({
      type: 'get',
      url: './tel.json',
      success: res => {
          const { tel } = res
          
          $.ajax({
            type: 'get',
            url: './tel.json',
            success: res => {
                // TODO: 请求...
            }
          })
      }
    })
  }
})
</script>

ES6 -> Promise

Promise 解决了上面的回调地狱

  1. Promise 特点

    • 对象的状态不受外界影响
    • 一旦状态改变, 就不会再变
  2. Promise 基本使用

/**
 * 接受一个函数作为参数
 * 参数函数 有两个参数: resolve, reject
 */
new Promise((resolve, reject) => {})

// 得到结果
[[Prototype]]: Promise
[[PromiseState]]: "pending"
[[PromiseResult]]: undefined
  1. Promise 状态

    • 看到上面 new Promise 返回结果里有一个[[PromsieState]]: pending
    • Promise 有三种状态: pending(进行中) fulfilled(已成功) rejected(以失败)
  2. 改变Promise 状态

    上面我们知道 Promise 特点就是: 对象状态不受外界影响, 那我们应该怎么改变 Promise 的状态呢?

    // 只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态
    
    // Promise 状态从 pending -> fulfilled
    const p1 = new Promise((resolve, reject) => resolve())
    console.log(p1) // [[PromiseState]]: "fulfilled"
    
    
    // Promise 状态从 pending -> rejected
    const p2 = new Promise((resolve, reject) => reject())
    console.log(p2) // [[PromiseState]]: "rejected"
    
    // Promise 状态一旦改变. 就不会再变
    const p3 = new Promise((resolve, reject) => {
      resolve()
      reject()
    })
    console.log(p3) // [[PromiseState]]: "fulfilled"
    
    const p4 = new Promise((resolve, reject) => {
      reject()
      resolve()
    })
    console.log(p4) // [[PromiseState]]: "rejected"
    
  3. 得到 Promise 的结果

    // 得到成功的结果
    const p1 = new Promise((resolve, reject) => {
      resolve('成功了')
    })
    console.log(p1) // [[PromiseResult]]: "成功了"
    p1.then(res => console.log(res))  // 成功了
    
    // 得到失败的结果
    const p2 = new Promise((resolve, reject) => {
      reject('失败了')
    })
    console.log(p2) // [[PromiseResult]]: "失败了"
    p2.catch(err => console.log(err)) // 失败了
    

使用 Promise 方法

  • then 方法
  1. Promise 实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的
    • then方法的第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数,它们都是可选的
    • then方法返回的是一个新的Promise实例, 状态是pending 可以采用链式写法
// then 接收两个参数, 都是可选的, 参数都是函数, 返回值也是 Promise 实例
const p1 = new Promise((resolve, reject) => {
    // 实参
    resolve('成功')
})

// [[PromiseState]]: "fulfilled"

// resolve 实参 传给 第一个函数的形参
p1.then((res) => {
    console.log(res, '成功时的回调') // 成功 成功时的回调
}, (err) => {
    console.log(err, '失败时的回调')
})

const p2 = new Promise((resolve, reject) => {
    reject('失败')
})

p2.then((res) => {
    console.log(res, '成功时的回调')
}, (err) => {
    console.log(err, '失败时的回调') // 失败 失败时的回调
})
  1. Promise 状态不改变 不会执行 .then() 里面的方法
new Promise((resolve, reject) => {
    
}).then((res) => {
    console.log(res, '成功时的回调')
}, (err) => {
    console.log(err, '失败时的回调')
})

// 返回结果 pending
[[Prototype]]: Promise
[[PromiseState]]: "pending"
[[PromiseResult]]: undefined

// 两次 then, resolve()
new Promise((resolve, reject) => {
    resolve()
}).then((res) => {
    console.log(res, '成功时的回调') // undefined '成功时的回调'
}, (err) => {
    console.log(err, '失败时的回调')
}).then((res) => {
    console.log(res, '成功2') // undefined '成功2'
}, (err) => {
    console.log(err, '失败2')
})

// 在 then 成功里面 return
new Promise((resolve, reject) => {
    resolve()
}).then((res) => {
    console.log(res, '成功时的回调') // undefined '成功时的回调'
    return 'yym'
}, (err) => {
    console.log(err, '失败时的回调')
}).then((res) => {
    console.log(res, '成功2') // yym 成功2
}, (err) => {
    console.log(err, '失败2')
})

// 返回结果 fulfilled
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: undefined

// 进入 失败 的回调
new Promise((resolve, reject) => {
    resolve()
}).then((res) => {
    console.log(res, '成功时的回调')
    throw new Error('出错了')
}, (err) => {
    console.log(err, '失败时的回调')
}).then((res) => {
    console.log(res, '成功2')
}, (err) => {
    console.log(err, '失败2') // Error: 出错了, '失败2'
})

  • catch 方法
  1. Promise.prototype.catch()方法是.then(null, rejection).then(undefined, rejection)的别名,用于指定发生错误时的回调函数
// catch 中的参数函数在什么时候执行

// 1. 当 promise 的状态从 pending -> rejected
new Promise((resolve, reject) => {
  reject()
}).catch(() => {
  console.log('失败了...') // 失败了...
})

// 2. 当 promise 代码执行出现错误
new Promise((resolve, reject) => {
    throw new Error('错误')
}).catch(() => {
    console.log('失败了...')
})
  1. [引用阮一峰ES6 promise] Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获
// 一共有三个 Promise 对象:一个由`getJSON()`产生,两个由`then()`产生。它们之中任何一个抛出的错误,都会被最后一个`catch()`捕获

getJSON('/post/1.json').then(function(post) {
  return getJSON(post.commentURL);
}).then(function(comments) {
  // some code
}).catch(function(error) {
  // 处理前面三个Promise产生的错误
});
  1. catch 最佳实践
    • 下面代码第二种写法可以捕获前面then方法执行中的错误,也更接近同步的写法(try/catch)。因此,建议总是使用catch()方法,而不使用then()方法的第二个参数

// bad
promise
  .then(function(data) {
    // success
  }, function(err) {
    // error
  });

// good
promise
  .then(function(data) { //cb
    // success
  })
  .catch(function(err) {
    // error
  });

  • finally() 方法
  1. finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作
  2. finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是fulfilled还是rejected。这表明,finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
// finally 实现
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 })
  );
};

Promise 其它使用

  • Promise.all
  1. Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例
// 接收一个数组为参数
Promise.all([a, b, c]).then(() => {})

// a b c 都是 promise 实例, 都从 pending -> fulfilled, Promise.all 才会返回成功 fulfilled
// 只要有一个 rejected, Promise.all 就会失败


const p1 = new Promise((resolve) => resolve(1))
const p2 = new Promise((resolve) => resolve(2))
const p3 = new Promise((resolve) => resolve(3))

Promise.all([p1, p2, p3]).then(res => {
    console.log(res, 'res是个数组')
})
// [1, 2, 3] 'res是个数组'

const p1 = new Promise((resolve) => resolve(1))
const p2 = new Promise((resolve) => resolve(2))
const p3 = new Promise((resolve, reject) => reject(3))

Promise.all([p1, p2, p3]).then(res => {
    console.log(res, 'res是个数组')
}).catch(reason => {
    console.log(reason, 'reason') // 3 'reason'
})
  • Promise.allSettled()

有时候,我们希望等到一组异步操作都结束了,不管每一个操作是成功还是失败,再进行下一步操作

  1. Promise.allSettled()方法接受一个数组作为参数,数组的每个成员都是一个 Promise 对象,并返回一个新的 Promise 对象。只有等到参数数组的所有 Promise 对象都发生状态变更(不管是fulfilled还是rejected),返回的 Promise 对象才会发生状态变更
const p1 = new Promise((resolve) => resolve(1))
const p2 = new Promise((resolve) => resolve(2))
const p3 = new Promise((resolve, reject) => reject(3))

Promise.allSettled([p1, p2, p3]).then(res => {
    console.log(res)
})

// 返回
[
    {status: 'fulfilled', value: 1}
    {status: 'fulfilled', value: 2}
    {status: 'rejected', reason: 3}
]

// 使用 Promise.all 实现 Promise.allSettled 效果
const p1 = new Promise((resolve, reject) => reject(1))
const p2 = new Promise((resolve) => resolve(2))
const p3 = new Promise((resolve, reject) => reject(3))

x = promiseList => promiseList.map(promise => promise.then(result => ({status: 'ok', value: result}),reason =>  ({status: 'not ok', reason}) ))

  Promise.all(x([p1, p2, p3])).then(res => console.log(res, 'res...'))
  // {status: 'not ok', reason: 1}
  // {status: 'ok', value: 2}
  // {status: 'not ok', reason: 3}
  • Promise.race()
  1. Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例
const p = Promise.race([p1, p2, p3]);
// 只要`p1`、`p2`、`p3`之中有一个实例率先改变状态,`p`的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给`p`的回调函数

const p1 = new Promise((resolve, reject) => reject(1))
const p2 = new Promise((resolve) => resolve(2))
const p3 = new Promise((resolve, reject) => reject(3))

Promise.race([p1, p2, p3]).then(res => {
    console.log(res, 'res')
}).catch(reason => {
    console.log(reason, 'reason') // 1, p1 先改变状态, 是 Promise 实例的返回值
})
  • Promise.any()

方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回

  1. 只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态
// `Promise.any()`不会因为某个 Promise 变成`rejected`状态而结束
const p1 = new Promise((resolve, reject) => reject(1))
const p2 = new Promise((resolve) => resolve(2))
const p3 = new Promise((resolve, reject) => reject(3))

Promise.any([p1, p2, p3]).then(res => {
    console.log(res) // 2 成功的
})

---

const p1 = new Promise((resolve, reject) => reject(1))
const p2 = new Promise((resolve, reject) => reject(2))
const p3 = new Promise((resolve, reject) => reject(3))

Promise.any([p1, p2, p3]).then(res => {
    console.log(res)
})
// 返回 rejected
[[Prototype]]: Promise
[[PromiseState]]: "rejected"
[[PromiseResult]]: AggregateError: All promises were rejected

  1. 就是Promise.any()不会因为某个 Promise 变成rejected状态而结束,必须等到所有参数 Promise 变成rejected状态才会结束

Promise.resolve()

有时需要将现有对象转为 Promise 对象,Promise.resolve()方法就起到这个作用

  1. 参数是一个 Promise 实例

    • 如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例
  2. 参数是一个thenable对象

    • thenable对象指的是具有then方法的对象
    • Promise.resolve()方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then()方法
    let thenable = {
      then: function(resolve, reject) {
        resolve(42);
      }
    };
    
    let p1 = Promise.resolve(thenable);
    p1.then(function (value) {
      console.log(value);  // 42
    });
    
  3. 参数是一个原始值

    • Promise.resolve()方法返回一个新的 Promise 对象,状态为resolved
    const p = Promise.resolve('Hello');
    
    p.then(function (s) {
      console.log(s)
    });
    // Hello
    
  4. 不带有任何参数

    • Promise.resolve()方法允许调用时不带参数,直接返回一个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
    

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)
});
// 出错了

Promise.try()

Promise.try为所有操作提供了统一的处理机制,所以如果想用then方法管理流程,最好都用Promise.try包装一下

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

推荐阅读更多精彩内容