JavaScript Promise异步编程
概述
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise
对象。
所谓Promise
,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
Promise
对象有以下两个特点。
(1)对象的状态不受外界影响。Promise
对象代表一个异步操作,有三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise
这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise
对象的状态改变,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise
对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
注意,为了行文方便,本章后面的resolved
统一只指fulfilled
状态,不包含rejected
状态。
有了Promise
对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise
对象提供统一的接口,使得控制异步操作更加容易。
Promise
也有一些缺点。首先,无法取消Promise
,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise
内部抛出的错误,不会反应到外部。第三,当处于pending
状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
如果某些事件不断地反复发生,一般来说,使用 Stream 模式是比部署Promise
更好的选择。
Promise相关概念
Promise状态
Promise的状态是Promise实例对象的一个属性[PromiseState]。
- pending 未决定的,promise的初始状态
- resolved / fullfilled 成功
- rejected 失败
promise的状态只能改变一次。
- 由pending改变为成功,成功结果一般称为value
- 由pending改变为失败,失败结果一般称为reason
Promise对象的值
promise对象的值是promise实例对象的一个属性[PromiseResult]。
保存了异步任务成功或失败的结果。
只有 resolve/reject 函数可以对该属性进行修改。
Promise的工作流程
Promise 运行机制
const p = new Promise((res, err) => {
setTimeout(() => {
console.log("promise running 1")
}, 1000)
res("promise fulfilled")
console.log("promise running 2")
})
console.log(p)
p.then((res)=>{
console.log("p.then res")
},(err)=>{
console.log("p.then err")
})
/*
> promise running 2
> Promise {PromiseState: "fulfilled"}
> p.then res
> promise running 1
*/
-
对于 Promise 中的代码,是在声明 p 时就会立即执行。尽管其中存在异步任务,异步任务本身也是立即执行的,异步任务的回调函数会被放到队列中等待条件满足后执行。
-
res() 和 err() 函数不会停止代码运行,只会改变 p 的状态,改变状态之后下面的代码会继续执行,直至 Promise 中的代码运行完毕。
-
Promse 出现报错会停止之后代码运行,并立即改变状态 Pending -》rejected。
const p = new Promise((res, err) => {
setTimeout(() => {
console.log("promise running 1")
res("promise fulfilled")
}, 5000)
console.log("promise running 2")
})
console.log(p)
p.then((res)=>{
console.log("p.then res")
},(err)=>{
console.log("p.then err")
})
console.log("end")
/*
> promise running 2
> Promise {PromiseState: ...}
> end
> promise running 1
> p.then res
*/
<iframe allowfullscreen="allowfullscreen" frameborder="0" height="300" src="//jsrun.net/G8EKp/embedded/js/light" width="100%"></iframe>
- 当使用 p.then 时,如果 p 中的 res() 或 err() 函数没有在异步回调函数中延迟改变 Promise 状态,p.then 会立即判断 Promise 状态并执行其后续逻辑。如果 p 中的 res() 或 err() 函数在异步回调函数中延迟改变 Promise 状态,那么 p.then 会等待 Promise 中的异步回调函数改变 Promise 状态后在运行,此时 p.then 也会变成异步任务进入队列等待执行。
- Promise 对象中的 PromiseState 属性类似于数据代理,其值取决于你什么时候查看。对于以上代码,尽管 Promise 对象在改变状态之前被输出。但是在你第一次查看时,其显示的是你查看时的状态。比如 5s 前查看输出结果时 pending,但是 5s 后点开查看结果就会变成 fulfilled。一旦第一次查看之后,其中的控制台输出值就固定了。
Promise相关api
Promise 构造函数
new Promise(excutor){}
-
excutor 函数:执行器
(resolve, reject) => {}
-
resolve 函数:内部定义成功时我们调用的函数
value => {}
-
reject 函数:内部定义失败时我们调用的函数
reason => {}
说明:executor 会在 Promise 内部立即同步调用, 异步操作在执行器中执行。
Promise.prototype.then
p.then(onResolved, onRejected)
- onResolved 函数:成功的回调函数
(value) => {}
- onRejected 函数:失败的回调函数
(reason) => {}
说明:不管成功还是失败,都会返回一个新的 Promise 对象。
Promise.prototype.catch
p.catch(onRejected)
- onRejected 函数:失败的回调函数
(reason) => {}
Promise.resolve
接收一个参数,并返回一个成功或失败的Promise对象。快速得到一个Promise对象。
let p = Promise.resolve(521)
// 如果传入的参数为 非Promise类型的对象,则返回结果为成功的Promise对象
console.log(p)
// 如果传入的参数为 Promise对象,则参数的结果决定了resolve的结果
// 也就是说传入的Promise为成功,那么resolve的状态和结果与传入一样
// 传入的Promise为失败,那么resolve的状态和结果也与传入一样
Promise.reject
返回一个失败的Promise
let p = Promise.reject(521)
let p2 = Promise.reject(new Promise((resolve, reject) => {
resolve('ok')
}))
console.log(p2)
Promise.all
传入几个Promise对象数组,返回一个新的Promise,只有所有传入的都成功返回的才成功,有一个失败返回的就失败。
let p1 = Promise.reject(new Promise((resolve, reject) => {
resolve('ok')
}))
let p2 = Promise.resolve(521)
let p3 = Promise.reject(123)
const res = Promise.all([p1,p2,p3])
// 如果都成功,那么返回的Promise也成功,三个成功的值以数组的形式存于返回的Promise中
// 如果有一个失败,那么返回的Promise就是失败的,结果值就为失败的结果值
Promise.race
传入一个n个Promise的数组,其返回结果由第一个改变状态的Promise来决定。
let p1 = Promise.reject(new Promise((resolve, reject) => {
resolve('ok')
}))
let p2 = Promise.resolve(521)
let p3 = Promise.reject(123)
const race = Promise.race([p1,p2,p3])
// 返回结果为第一个改变状态的Promise相同的Promise
util.promise
这是nodejs中的方法,将错误优先的回调风格的函数(err,value)传入,将会返回一个promise的版本。
const util = require('util')
const fs = require('fs')
const newReadFile = util.promisify(fs.readFile)
newReadFile('./promise封装fs.txt')
.then(value => console.log(value.toString()))
Promise关键问题
Promise状态的改变
- resolve(value):将Promise的状态由pending改变为resolved
- reject(reason):将Promise的状态由pending改变为rejected
- 抛出异常:将Promise的状态由pending改变为rejected
const p = new Promise((resolve, reject) => {
// resolve('ok')
// reject('error')
throw '出问题了'
})
Promise执行多个回调
只要状态发生改变,回调才会执行,多个回调也会执行。
let p = Promise.reject(new Promise((resolve, reject) => {
resolve('ok')
}))
// 指定回调1
p.then(value => {
alert(value)
})
// 指定回调2
p.then(value => {
console.log(value)
})
改变Promise状态和回调函数的先后问题
- 当Promise中执行同步任务时,先改变状态,然后执行回调函数。
let p = Promise.reject(new Promise((resolve, reject) => {
resolve('ok')
}))
p.then(value => {
console.log(value)
})
- 当Promise中执行异步任务时,回调函数先执行,改变状态后执行。
let p = Promise.reject(new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok')
})
}), 3000)
p.then(value => {
console.log(value)
})
Promise中then的返回结果
- 返回结果非Promise对象,会将返回的值包装成成功的Promise的结果。
let p = Promise.resolve(321)
let p2 = p.then((value) => {
return 'noPromise'
})
console.log(p2)
- 如果未返回结果,那么默认会返回成功的状态结果值为undefined的Promise对象。
let p = Promise.resolve(321)
let p2 = p.then((value) => {
})
console.log(p2)
- 如果返回Promise则与返回与Promise保持一致状态和结果的Promise对象。
let p = Promise.resolve(321)
let p2 = p.then((value) => {
return Promise.resolve('test')
// return Promise.reject('test')
})
console.log(p2)
串联多个任务
let p = Promise.reject(new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok')
})
}), 3000)
p.then(value => {
return new Promise((resolve, reject) => {
resolve("success")
})
}).then(value => {
console.log(value)
})
// 最后输出success
异常穿透
只需要在链式的最后捕获异常即可,这就是异常穿透,之中的环节出现异常都会被最后的catch捕获
let p = Promise.reject(new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok')
})
}), 3000)
p.then(value => {
console.log(111)
}).then(value => {
console.log(222)
}).then(value => {
console.log(333)
}).catch(reason => {
console.warn(reason)
})
中断Promise链
当有一堆链式Promise链时,如果想中途停止,需要在停止的地方返回一个pending状态的Promise才可
let p = Promise.reject(new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok')
})
}), 3000)
p.then(value => {
console.log(111)
// 中断后续的Promise链
return new Promise(() => {})
}).then(value => {
console.log(222)
}).then(value => {
console.log(333)
}).catch(reason => {
console.warn(reason)
})
async函数
async修饰的函数的返回值为promise对象,其返回逻辑与then相同。
async function test() {
return Promise.resolve('test')
}
let res = test()
console.log(res)
await
- await修饰Promise对象时,会直接返回Promise的成功值。
- await修饰其他值时,会直接将这个值返回。
注意:await必须写在async函数中,但是async中可以没有await。
async function test() {
let res = await Promise.resolve('test')
console.log(res)
}
test()
Promise常见封装
Promise封装定时器
// 封装生成1-100随机数的函数
function rand(m, n) {
return Math.ceil(Math.random() * (n - m + 1)) + m - 1
}
// 获取button对象
const btn = document.querySelector('button')
// 设置点击事件
btn.addEventListener('click', () => {
// 使用promise封装一个异步操作
// resolve为成功函数
// reject为失败函数
const p = new Promise((resolve, reject) => {
setTimeout(() => {
let n = rand(1, 100);
if (n <= 30) {
resolve(n) // 将promise对象的状态设置为 成功
} else {
reject(n) // 将promise对象的状态设置为 失败
}
},1000)
})
// 调用then方法,其中有两个回调函数,第一个为promise为成功时的回调函数,第二个为promise为失败时的回调函数。
// 只写第一个时,就是成功时的回调函数
p.then((value) => {
alert('中奖,抽取号码为 ' + value)
}, (value) => {
alert('未中奖, 抽取号码为 ' + value)
})
})
Promise封装fs
const fs = require('fs')
const p = new Promise((resolve, reject) => {
fs.readFile('./promise封装fs.txt', (err, data) => {
if (err) reject(err)
resolve(data)
})
})
p.then(value => {
console.log('成功:' + value.toString())
}, res => {
console.log('失败:' + res)
})
function mineReadFile(path) {
return new Promise((resolve, reject) => {
require('fs').readFile(path, (err, data) => {
if (err) reject(err)
resolve(data)
})
})
}
mineReadFile('./promise封装fs.txt')
.then(
value => console.log(value.toString()),
res => console.warn(res)
)
Promise封装Ajax
const btn = document.querySelector('button')
btn.addEventListener('click', function () {
const p = new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open('GET', 'https://api.ooomn.com/api/xwzc')
xhr.send()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response)
} else {
reject(xhr.status);
}
}
}
})
p.then(value => {
console.log('成功:' + value);
}, res => {
console.log('失败:' + res);
})
})
function sendAjax(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open('GET', url)
xhr.send()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response)
} else {
reject(xhr.status);
}
}
}
})
}
const btn = document.querySelector('button')
btn.addEventListener('click', () => {
sendAjax('https://api.ooomn.com/api/xwzc')
.then(value => console.log(value))
})
Promise解决回调地狱
回调地狱形式
const fs = require('fs')
fs.readFile('./1.txt', (err, data1) => {
if(err) throw err
fs.readFile('./2.txt', (err, data2) => {
if(err) throw err
fs.readFile('./3.txt', (err, data3) => {
if(err) throw err
let res = data1 + data2 + data3
console.log(res)
})
})
})
// 结果123
使用Promise解决回调地狱1
const fs = require('fs')
const util = require('util')
const mineReadFile = util.promisify(fs.readFile)
async function main() {
const data1 = await mineReadFile('./1.txt')
const data2 = await mineReadFile('./2.txt')
const data3 = await mineReadFile('./3.txt')
let res = data1 + data2 + data3
console.log('结果:' + res)
}
使用Promise解决回调地狱2
const fs = require('fs')
let p = new Promise((resolve, reject) => {
fs.readFile('./1.txt',(err, data) => {
if(err) reject(err)
resolve(data)
})
})
p.then(value => {
return new Promise((resolve, reject) => {
fs.readFile('./2.txt',(err, data) => {
if(err) reject(err)
resolve(value + data)
})
})
}).then(value => {
return new Promise((resolve, reject) => {
fs.readFile('./3.txt',(err, data) => {
if(err) reject(err)
resolve(value + data)
})
})
}).then(value => {
console.log(value)
})
标签:异步,resolve,console,log,value,JS,Promise,reject
From: https://www.cnblogs.com/brokyz/p/16750137.html