其他章节请看:

es6 快速入门 系列

Promise

Promise 是一种异步编程的选择

初步认识Promise

用 Promise 来实现这样一个功能:发送一个 ajax,返回后输出 json 数据。请看示例:

const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
let json = {success: true, data:{}}
resolve(json);
}, 3000);
});
const resolveFn = value => {
console.log(value)
};
const rejectFn = () => {} promise1.then(resolveFn, rejectFn) // { success: true, data: {} }

三秒后输出 json 数据。

Promise 中文翻译是承诺。首先用 Promise 构造函数创建一个承诺,承诺异步操作在未来的某时刻完成,接着给承诺(promise1)绑定”已完成“状态的回调 resolveFn,以及”已拒绝“状态的回调 rejectFn。3秒后返回 json 数据,将承诺的状态改为”已完成“(resolve(json)),对应的回调函数(resolveFn)被调用执行。

每个 Promise 都会经历一个短暂的生命周期,首先是进行中(pending)的状态,一旦异步操作执行结束,Promise则变成已处理的状态。在前面示例中,执行 new Promise() 创建一个 Promise,是进行中的状态,操作结束后,Promise 可能会进入到以下两个状态中的其中一个:

  • 已完成(Fulfilled) Promise异步操作成功完成

    • 调用 resolve() 进入此状态
  • 已拒绝(Rejected) Promise异步操作未能成功完成
    • 调用 reject() 进入此状态

创建 Promise

通过 new Promise(executor) 可以创建一个新的 Promise。新的 Promise 在没有 resolve 之前,这个 Promise 的状态是进行中(或未解决)。

executor(执行器)是一个双参函数,参数为 resolve 和 reject。Promise 构造器将会在返回新对象之前执行 executor,并传入 resolve 和 reject 函数。请看示例:

const promise1 = new Promise((resolve, reject) => {
console.log(11)
})
console.log(22)
// 11 22

接着看这个示例:

const p1 = new Promise((resolve, reject) => {
resolve()
console.log(11)
})
p1.then(() => {
console.log('then')
})
console.log(22) // 11 22 then

调用 resolve() 后会触发一个异步操作,所以先执行同步(输出 11 22),最后输出 then。类似这段代码:

const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('then')
})
console.log(11)
}) console.log(22)
// 11 22 then

then() 方法的两个参数都是可选的,可以按照任意组合方式监听 Promise。请看示例:

const promise1 = new Promise((resolve, reject) => {
resolve(1) // {1}
}) promise1.then(() => {
console.log('完成')
}) promise1.then(() => {
console.log('完成')
}, () => {
console.log('拒绝')
}) promise1.then(null, () => {
console.log('拒绝')
}) promise1.catch(() => {
console.log('catch')
}) // 完成 完成

上面前 3 次 then() 调用操作的是同一个 Promise。第一个只监听了完成,错误是不报告;第二个同时监听了完成和拒绝;第三个只监听了拒绝,成功时不报告;

如果改为reject(1)({1}),则输出“拒绝 拒绝 catch”。

无论何时都可以添加新的已完成或已拒绝处理程序。请看示例:

const promise1 = new Promise((resolve, reject) => {
resolve();
}); promise1.then(function(cnt){
console.log(1)
promise1.then(function(cnt){ // {1}
console.log(2)
})
console.log(3)
return 4
}).then(v => {
console.log(v)
})
// 1 3 2 4

这段代码在完成处理程序中,向同一个 Promise 添加了另一个完成处理程序(行{1})。

Tip: then() 方法指定的回调函数将在当前脚本所有同步任务执行完才会处理。

创建已处理的 Promise

创建未处理的 Promise 最好方法是使用 Promise 构造函数,但如果想用 Promise 表示一个已知值,可以用Promise.resolve() 或者 Promise.reject()。

Promise.resolve

Promise.resolve(value),只接受一个参数并返回一个 Promise。

let p1 = Promise.resolve(11) // {1}
p1.then(v => {
console.log(v)
})
console.log(22) // 输出:22 11

行{1}等价于let p1 = new Promise(resolve => resolve(11))。

如果给 Promise.resolve() 方法传入一个 Promise,那么这个 Promise 会被直接返回。请看示例

let p1 = new Promise((resolve, reject) => {
resolve(1) // {1}
})
// resolve另一个promise
let p2 = Promise.resolve(p1)
console.log(p1 == p2)
p2.then(v => {
console.log('resolve')
},v => {
console.log('reject')
}) // true resolve

如果将行{1}改为reject(1),输出“true reject”。

利用此特性,可以将不是 Promise 的值转为 Promise。

Promise.resolve() 方法允许调用时不带参数,直接返回一个resolved 状态的 Promise 对象。就像这样:

let p1 = Promise.resolve()
p1.then(v => {
console.log(v)
})
console.log(22)
// 输出:22 undefined

非 Promise 的 Thenable 对象

Promise.resolve() 可以接受非 Promise 的 Thenable 对象作为参数,返回的 Promise 将采用 Thenable 对象的最终状态。请看示例:

// Thenable 对象指:拥有 then() 方法并且接受 resolve 和 reject 两个参数的普通对象
let thenable = {
then: function(resolve, reject){
reject(1)
}
}
let p1 = Promise.resolve(thenable) p1.then(v => {
console.log('resolve')
}).catch(v => {
console.log('reject')
}) // reject

这段代码,虽然调用的是 Promise.resolve(),但 thenable 的状态是已拒绝(reject(1)),所以最后输出 reject。

Promise.reject

Promise.reject() 方法返回一个带有拒绝原因的 Promise 对象。请看示例:

let p1 = Promise.reject(11)
p1.catch(v => {
console.log(v)
})
console.log(22) // 输出:22 11

Promise.reject() 用法比 Promise.resolve() 简单很多。

比如给 Promise.reject() 方法传入一个 Promise,效果与 Promise.resolve() 不相同。请看示例:

let p1 = new Promise((resolve, reject) => {
reject(1)
})
let p2 = Promise.reject(p1)
console.log(p1 == p2) // false

再比如给 Promise.reject() 方法传入一个 thenable,效果与 Promise.resolve() 也不相同。请看示例:

let thenable = {
then: function(resolve, reject){
resolve(1)
}
}
let p1 = Promise.reject(thenable) p1.then(v => {
console.log('resolve')
}).catch(v => {
console.log('reject')
}) // reject

执行器错误

如果执行器内部抛出错误,则 Promise 的拒绝处理程序就会被调用,例如:

let p1 = new Promise(function(resolve, reject){
throw new Error('fail')
}) p1.catch(v => {
console.log(v.message) // fail
})

这段代码,执行器故意抛出一个错误,每个执行器中都隐含一个 try-catch 块,所以错误会被捕获并传入给已拒绝回调。此例等价于:

let p1 = new Promise(function(resolve, reject){
try{
throw new Error('fail')
}catch(e){
reject(e)
}
}) ...

串联 Promise

将 Promise 串联起来能实现更复杂的异步特征:

let p1 = new Promise((resolve, reject) => {
resolve('10')
}) p1.then(v => {
console.log(v)
}).then(() => {
console.log('finished')
})

每次调用 then() 方法或 catch() 方法时,实际上会创建并返回另一个 Promise,只有当第一个 Promise 完成或拒绝后,第二个才会被解决,依此类推。

将这个示例拆开,看起来像这样:

let p1 = new Promise((resolve, reject) => {
resolve('10')
}) let p2 = p1.then(v => {
console.log(v)
}) p2.then(() => {
console.log('finished')
})

捕获错误

在完成或拒绝处理程序中可能发生错误,而 Promise 链可以捕获这些错误。请看示例:

let p1 = new Promise((resolve, reject) => {
resolve('10')
}) p1.then(() => {
throw new Error('fail')
}).catch((e) => {
console.log(e.message)
}) // 输出:fail

这段代码在完成处理程序中抛出一个错误。如果在拒绝处理程序中抛出错误,也可以通过相同的方式接收:

let p1 = new Promise((resolve, reject) => {
reject('10')
}) p1.catch(() => {
throw new Error('fail')
}).catch((e) => {
console.log(e.message)
}) // 输出:fail

尽量在 Promise 链的末尾留一个拒绝处理程序,以保证能正确处理所有可能发生的错误。请看示例:

如果没有拒绝处理程序,代码可能会这样:

let p1 = new Promise((resolve, reject) => {
resolve('10')
}) p1.then(() => {
console.log(1) // {1}
}).then(() => {
console.log(2) // {2}
}).then(() => {
console.log(3) // {3}
})

其中三个完成处理程序都有可能出错,我们可以在末尾添加一个已拒绝处理的程序对这个链式统一处理,就像这样:

let p1 = new Promise((resolve, reject) => {
resolve('10')
}) p1.then(() => {
throw new Error('fail')
console.log(1)
}).then(() => {
console.log(2)
}).then(() => {
console.log(3)
}).catch(e => {
console.log(e.message)
}) // 输出:fail

这段代码是第一个完成处理程序报错,由于只有末尾才有已拒绝的处理,所以只输出 fail。

传递数据

Promise 链的另一个重要特性是可以给下游的 Promise 传递数据。请看示例:

let p1 = new Promise((resolve, reject) => {
resolve(1)
}) p1.then(v => {
console.log(v)
return v + 1
}).then(v => {
console.log(v)
})
// 输出:1 2

在拒绝处理程序中也可以做相同的事:

let p1 = new Promise((resolve, reject) => {
reject(1)
}) p1.catch(v => {
console.log(v)
return v + 1
}).then(v => {
console.log(v)
})
// 输出:1 2

拒绝处理中返回值仍然可以在下一个Promise的完成处理程序中使用,必要时,即使其中一个Promise失败,也能恢复整条链的执行。

在 Promise 链中返回 Promise

前面我们通过返回值给下游 Promise 传递数据,如果返回值是 Promise 对象,则会通过一个额外的步骤来确定下一步该怎么走。请看示例:

let p1 = new Promise((resolve, reject) => {
reject(1)
}) let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(10) // {1}
}, 3000)
}) p1.catch(v => {
console.log('等待3秒')
return p2
}).then(v => {
console.log(`resolve: ${v}`)
}, v => {
console.log(`reject: ${v}`)
}) /*
等待3秒
// 等待3秒后输出
resolve: 10
*/

这段代码,在 Promise 链中返回一个 Promise(p2),由于 p2 的状态是已完成({1}),所以下一步则进入已完成处理程序。

响应多个Promise

es6 提供了 Promise.all() 和 Promise.race() 两个方法来监听多个 Promise。

Promise.all()

Promise.all 只接收一个参数并返回一个Promise,该参数是含有多个受监视Promise的可迭代对象(例如数组),只有当所有 Promise 都被解决,返回的 Promise 才会被解决。请看示例:

let p1 = new Promise((resolve, reject) => {
resolve(1)
}) let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2)
}, 3000) }) let p3 = Promise.all([p1, p2]) p3.then(value => {
console.log(Array.isArray(value)) // {1}
console.log(value)
}).catch(v => {
console.log(Array.isArray(v))
console.log(v)
}) // true
// [1, 2]

这段代码,Promise.all 监听了两个 Promise,其中一个需要过3秒才被置为已解决,当两个 Promise 都被解决,才会输出结果。其中 value({1})是数组。

如果被 Promise.all 监听的其中一个被拒绝,那么不用等所有 Promise 都完成就会立即被拒绝。在上面示例的基础上,将 resolve(1) 改为 reject(1),立即输出false 1,无需等待另一个 Promise 解决。拒绝处理程序总是接受一个值而非数组。

Promise.race()

Promise.race() 与 Promise.all() 类似,不同之处是只要有一个被解决,返回的 Promise 就被解决。请看示例:

let p1 = new Promise((resolve, reject) => {
resolve(1)
}) let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2)
}, 3000) }) let p3 = Promise.race([p1, p2])
console.log(p3 === p1)
p3.then(v => {
console.log(Array.isArray(v))
console.log(`resolve, ${v}`)
}).catch(v => {
console.log(Array.isArray(v))
console.log(`reject, ${v}`)
}) /*
false
false
resolve, 1
*/

无需等待 p2 被解决,立刻输出。实际上,传给 Promise.race() 方法的 Promise 会进行竞选,以决定哪一个先被解决,如果先解决的是已完成 Promise,则返回已完成的 Promise,如果先解决的是已拒绝的 Promise,则返回已拒绝的Promise。请看示例:

let p1 = new Promise((resolve, reject) => {
reject(1)
}) let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('p2 resolve')
resolve(2)
}, 3000)
}) let p3 = Promise.race([p1, p2]) p3.then(v => {
console.log(Array.isArray(v))
console.log(`resolve, ${v}`)
}).catch(v => {
console.log(Array.isArray(v))
console.log(`reject, ${v}`)
})
/*
false
reject, 1
p2 resolve
*/

p2 虽然被忽略,但仍会执行。

其他章节请看:

es6 快速入门 系列

es6 快速入门 系列 —— promise的更多相关文章

  1. es6快速入门 系列 - async

    其他章节请看: es6 快速入门 系列 async 前文我们已经知道 promise 是一种异步编程的选择.而 async 是一种用于执行异步任务更简单的语法. Tip:建议学完 Promise 在看 ...

  2. es6 快速入门 系列 —— 变量声明:let和const

    其他章节请看: es6 快速入门 系列 变量声明:let和const 试图解决的问题 经典的 var 声明让人迷惑 function demo1(v){ if(v){ var color='red' ...

  3. es6 快速入门 系列 —— 类 (class)

    其他章节请看: es6 快速入门 系列 类 类(class)是 javascript 新特性的一个重要组成部分,这一特性提供了一种更简洁的语法和更好的功能,可以让你通过一个安全.一致的方式来自定义对象 ...

  4. es6 快速入门 系列 —— 对象

    其他章节请看: es6 快速入门 系列 对象 试图解决的问题 写法繁杂 属性初始值需要重复写 function createPeople(name, age){ // name 和 age 都写了 2 ...

  5. es6 快速入门 系列

    es6 快速入门(未完结,持续更新中...) 前言 为什么要学习es6 es6对于所有javaScript开发者来说,非常重要 未来,es6将构成javaScript应用程序的基础 es6中很多特性, ...

  6. es6 快速入门 —— 函数

    其他章节请看: es6 快速入门 系列 函数 函数是所有编程语言的重要组成部分,es6之前函数语法一直没什么变化,遗留了许多问题,javaScript开发者多年来不断抱怨,es6终于决定大力度更新函数 ...

  7. python 全栈开发,Day88(csrf_exempt,ES6 快速入门,Vue)

    BBS项目内容回顾 1. 登陆页面 1. 验证码 1. PIL(Pillow) 2. io 2. ORM 1. 增删改查 3. AJAX $.ajax({ url: '', type: '', dat ...

  8. vue 快速入门 系列 —— 实例方法(或 property)和静态方法

    其他章节请看: vue 快速入门 系列 实例方法(或 property)和静态方法 在 Vue(自身) 项目结构 一文中,我们研究了 vue 项目自身构建过程,也知晓了 import Vue from ...

  9. vue 快速入门 系列 —— 侦测数据的变化 - [基本实现]

    其他章节请看: vue 快速入门 系列 侦测数据的变化 - [基本实现] 在 初步认识 vue 这篇文章的 hello-world 示例中,我们通过修改数据(app.seen = false),页面中 ...

随机推荐

  1. 段间跳转之TSS段

    TR寄存器,TSS描述符,TSS段 TR寄存器与普通的段寄存器一样都有可见部分和不可见部分.TR的可见部分为16位为其段选择子,不可见部分是32位的TSS基地址和16位的大小. TSS描述符存在GDT ...

  2. 黄衫女子,黄衫好.png

    正想着团队项目中数据该如何解析,就收到了来自软工课程组的一件小黄衫,真是意外之喜.详问其来源,竟是因结对项目做的"较好"而来,顿感受之有愧. 结对项目是两人对文件系统的一个小模拟, ...

  3. Alpha事后分析

    设想和目标 1. 我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述? 我们的软件的功能主要是让一些基于表单识别的项目(如微软智能表单识别项目)减少在数据生成方面上浪费的 ...

  4. CRM系统有哪几种常见类型?

    随着市场的快速变化,客户开始变得越来越重要,因此CRM客户管理系统开始逐渐被企业所认可.从CRM系统进入中国市场到现在十余年的发展中,越来越多的CRM厂商开始出现.为了满足不同行业.不同类型的企业的需 ...

  5. Docker部署微服务项目

    测试包准备工作 1.spring.io或者ide创建demo工程 spring官网 2.本地demo代码,打包成jar包 使用Dockerfile构建微服务镜像 3.将jar包上传到你的vps lin ...

  6. [Linux] Linux C编程一站式学习 Part.2

    C语言本质 计算机中数的表示 浮点数:符号位+指数部分(2的多少次方)+尾数部分(小数点后的数字) 用偏移的指数(Biased Exponent)表示负指数 正规化(Normalize):尾数部分最高 ...

  7. vi/vim输入中文乱码,无法输入中文解决方法

    vi/vim输入中文乱码,无法输入中文解决方法 编辑/etc/vimrc或者/etc/virc,加入以下内容即可 set encoding=UTF-8 set langmenu=zh_CN.UTF-8 ...

  8. Java EnumMap 实现类

    EnumMap 实现类 因为 HashMap 是一种通过对 key 计算 hashCode(),通过空间换时间的方式,直接定位到 value 所在的内部数组的索引,因此,查找效率非常高. 如果作为 k ...

  9. 重新整理 .net core 实践篇—————配置文件之环境配置[九]

    前言 在当今在互联网微服务比较适用的情况下,docker 可以说一个利器.每次我们打包docker的时候都是适用docker 的配置文件,那么配置文件里面会设置环境变量,这个时候需要我们的应用能够识别 ...

  10. 06.ElementUI 2.X 源码学习:源码剖析之工程化(一)

    0x.00 前言 在用了5章篇幅 ElementUI源码学习:从零开始搭建Vue组件库汇总 讲解了如何编写一个组件.发布npm以及生成展示文档之后.接下来将分析Element项目的代码结构,学习其工程 ...