3.04.24Promise对象
1.使用Promise对象
- Promise对象是一个对象,也是一个构造函数
- 该对象用于解决异步编程
- 同步的代码执行:一种按照从上往下执行顺序的编码,可知js是一种同步编程,当代码在执行一件事情时,你不能进行别的操作,比如:你有一个复杂的函数需要运行一段时间,那么这时候页面有输入框的情况,你不能进行输入的操作。
- 异步的代码执行:异步的代码执行就是用于解决同步带来的问题的。异步编程一般是这样的,从上往下执行顺序的过程中遇到需要等待的事件,就给其一个等待的状态A,然后去执行后面的事情,等状态A改变了,就触发某个事件X,就执行事件X。比如,我们的ajax就是这样的道理。
- 用法:
// 定义布尔值(条件)
let isMouseDown = true;
// 创建了promise
let p = new Promise((resolve,reject) => {
if(isMouseDown){
resolve("符合条件");
}else {
reject("失败");
}
})
// 把promise的API拿出来
p.then(
(data)=> {// 代表符合条件的回调函数,如果符合条件,那么执行第一个回调函数
console.log('1.0',data);
},
(err) => { // 代表失败的回调函数,如果不符合条件,那么执行第二个回调函数
console.log('2.0',err);
}
)
//控制台打印:1.0 符合条件
- 分析:
- Promise构造函数需要一个函数A作为参数
- 这个函数A参数需有两个参数,第一个参数等于Promise.then的第一个参数,第二个参数等于Promise.then的第二个参数
- Promise.then需有两个参数,第一个参数为一个函数,第二个参数也是一个函数
- Promise.then的第一个参数函数习惯用于符合函数A的逻辑时执行,Promise.then的第二个参数函数习惯用于不符合函数A的逻辑时执行
- 故Promise构造函数构造出来的实例需要结合Promise.then去发挥作用
2.认识Promise
promise的使用如上所示,promise.then()总是会捕捉到promise的状态做出相对应的函数触发。
状态的改变从而触发函数,这就像各种事件函数的触发, 事件处理程序是一种特殊类型的回调。回调只是传递给另一个函数的函数,期望回调将在适当的时间被调用。回调曾经是 JavaScript 中实现异步函数的主要方式。 而这往往会产生"回调地狱"的情况,"回调地狱"就是在回调函数里又进行回调,不停地回调,形成回调地狱。
由于回调地狱的问题,JavaScript 中异步编程的就采用promise, JavaScript 中异步编程的基础是 promise。
理解promise:
- 本质上,Promise 是一个对象,代表操作的中间状态 —— 正如它的单词含义 ‘承诺’ ,它保证在未来可能返回某种结果。虽然 Promise 并不保证操作在何时完成并返回结果,但是它保证当结果可用时,你的代码能正确处理结果,当结果不可用时,你的代码同样会被执行,来优雅的处理错误。
- 通常你不会对一个异步操作从开始执行到返回结果所用的时间感兴趣(除非它耗时过长),你会更想在任何时候都能响应操作结果,当然它不会阻塞其余代码的执行就更好了。
- 你与 Promise 常见的交互之一就是 Web API 返回的 promise 对象。让我们设想一个视频聊天应用程序,该程序有一个展示用户的朋友列表的窗口,可以点击朋友旁边的按钮对朋友视频呼叫。
- 该按钮的处理程序调用 getUserMedia() 来访问用户的摄像头和麦克风。由于 getUserMedia() 必须确保用户具有使用这些设备的权限,并询问用户要使用哪个麦克风和摄像头(或者是否仅进行语音通话,以及其他可能的选项),因此它会产生阻塞,直到用户做出所有的决定,并且摄像头和麦克风都已启用。此外,用户可能不会立即响应权限请求。所以 getUserMedia() 可能需要很长时间。
- 由于 getUserMedia() 是在浏览器的主线程进行调用,整个浏览器将会处于阻塞状态直到 getUserMedia() 返回,这是不应该发生的;不使用Promise,浏览器将处于不可用状态直到用户为摄像头和麦克风做出决定。因此 getUserMedia() 返回一个Promise对象,即 promise,一旦 MediaStream 流可用才去解析,而不是等待用户操作、启动选中的设备并直接返回从所选资源创建的 MediaStream 流。
上述视频聊天应用程序的代码可能像下面这样:
function handleCallButton(evt) {
setStatusMessage("Calling...");
navigator.mediaDevices.getUserMedia({video: true, audio: true})
.then(chatStream => {
selfViewElem.srcObject = chatStream;
chatStream.getTracks().forEach(track => myPeerConnection.addTrack(track, chatStream));
setStatusMessage("Connected");
}).catch(err => {
setStatusMessage("Failed to connect");
});
}
//这个函数在开头调用 setStatusMessage() 来更新状态显示信息"Calling...", 表示正在尝试通话。接下来调用 getUserMedia(),请求具有视频及音频轨的流,一旦获得这个流,就将其显示在"selfViewElem"的video元素中。接下来将这个流的每个轨道添加到表示与另一个用户的连接的 WebRTC。在这之后,状态显示为"Connected"。
//如果getUserMedia()失败,则catch块运行。这使用setStatusMessage()更新状态框以指示发生错误。
//这里重要的是getUserMedia()调用几乎立即返回,即使尚未获得相机流。即使handleCallButton()函数向调用它的代码返回结果,当getUserMedia()完成工作时,它也会调用你提供的处理程序。只要应用程序不假设流式传输已经开始,它就可以继续运行。
- 这就是promise进行异步编程的关键点。
3.Promise.then()与Promise.catch()
1.Promise.then()
- then() 方法返回一个 Promise 对象。它最多需要有两个参数:Promise 的成功和失败情况的回调函数。
- 语法:
p.then(onFulfilled[, onRejected]);
p.then(value => {
// fulfillment
}, reason => {
// rejection
});
- 参数:
- onFulfilled 可选 当 Promise 变成接受状态(fulfilled)时调用的函数。该函数有一个参数,即接受的最终结果(the fulfillment value)。如果该参数不是函数,则会在内部被替换为 (x) => x,即原样返回 promise 最终结果的函数
- onRejected 可选 当 Promise 变成拒绝状态(rejected)时调用的函数。该函数有一个参数,即拒绝的原因(rejection reason)。 如果该参数不是函数,则会在内部被替换为一个 “Thrower” 函数 (it throws an error it received as argument)。
- 返回值 具体的返回值 与 then 中的回调函数有关:
- 返回了一个值,那么 then 返回的 Promise 将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。
- 没有返回任何值,那么 then 返回的 Promise 将会成为接受状态,并且该接受状态的回调函数的参数值为 undefined。
- 抛出一个错误,那么 then 返回的 Promise 将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数的参数值。
- 返回一个已经是接受状态的 Promise,那么 then 返回的 Promise 也会成为接受状态,并且将那个 Promise 的接受状态的回调函数的参数值作为该被返回的Promise的接受状态回调函数的参数值。
- 返回一个已经是拒绝状态的 Promise,那么 then 返回的 Promise 也会成为拒绝状态,并且将那个 Promise 的拒绝状态的回调函数的参数值作为该被返回的Promise的拒绝状态回调函数的参数值。
- 返回一个未定状态(pending)的 Promise,那么 then 返回 Promise 的状态也是未定的,并且它的终态与那个 Promise 的终态相同;同时,它变为终态时调用的回调函数参数与那个 Promise 变为终态时的回调函数的参数是相同的。
- 因为then()的返回值也是一个promise对象,所以可以链式调用,即,p.then().then().then()…
2.Promise.catch()
- catch() 方法返回一个Promise (en-US),并且处理拒绝的情况。它的行为与调用Promise.prototype.then(undefined, onRejected) 相同。 (事实上, calling obj.catch(onRejected) 内部calls obj.then(undefined, onRejected)).
- 语法:
p.catch(onRejected);
p.catch(function(reason) {
// 拒绝
});
- 参数:
- onRejected 当Promise 被rejected时,被调用的一个Function。该函数拥有一个参数:reason。如果 onRejected 抛出一个错误或返回一个本身失败的 Promise , 通过 catch() 返回的Promise 被rejected;否则,它将显示为成功(resolved)。
- 返回值 一个Promise.
- 用法:
let isMouseDown = true;
// // 创建了pro
let pro = new Promise((resolve,reject)=>{
if(isMouseDown){
resolve();
}else {
reject();
}
})
//利用pro实例对象,执行相关的业务逻辑
pro.then(
()=> {
console.log("第一步");
}
)
.then(
()=> {
console.log("第二步");
}
)
.catch(
()=> {
// 捕捉异常
console.log("执行不符合条件的逻辑");
}
)
.finally(
()=> {
console.log("最后一定执行的逻辑");
}
)
- promise对象有一个finally方法:finally() 方法返回一个Promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。这为在Promise是否成功完成后都需要执行的代码提供了一种方式。这避免了同样的语句需要在then()和catch()中各写一次的情况。
4.Promise对象
- Promise 对象用于表示一个异步操作的最终完成 (或失败)及其结果值。
- 一个 Promise 对象代表一个在这个 promise 被创建出来时不一定已知的值。它让您能够把异步操作最终的成功返回值或者失败原因和相应的处理程序关联起来。 这样使得异步方法可以像同步方法那样返回值:异步方法并不会立即返回最终的值,而是会返回一个 promise,以便在未来某个时候把值交给使用者。
- promise 一般有三种状态:
- 待定(pending): 初始状态,既没有被兑现,也没有被拒绝。
- 已兑现(fulfilled): 意味着操作成功完成。
- 已拒绝(rejected): 意味着操作失败。
- 因为其是一个对象,所以他身上有一些属性与方法,Promise对象身上还有一些静态方法(只给构造函数访问的属性与方法)
5.Promise对象的一些静态方法
- Promise.race(iterable)
- 参数:iterable 可迭代对象,类似Array。
- 返回值:一个待定的 Promise 只要给定的迭代中的一个promise解决或拒绝,就采用第一个promise的值作为它的值,从而异步地解析或拒绝(一旦堆栈为空)。
- 用法:
<script>
let init = (isMove = true,time = 2,msg) => {
return new Promise((resolve,reject)=> {
let d = setTimeout(()=> {
if(isMove){
resolve(msg);
}else {
reject();
}
},time); // 1000毫秒 = 1秒
})
}
// 初始化3个promise
// init(布尔值,毫秒,字符串)
let p1 = init(true,1000,"执行第一个任务");
let p2 = init(true,2000,"执行第二个任务");
let p3 = init(true,500,"执行第三个任务");
//race
// 利用Promise执行效率最高的任务
Promise.race([p1,p2,p3]).then(
message => {
console.log(message);//执行第三个任务
}
)
</script>
- Promise.all(iterable)
- 这个方法返回一个新的promise对象,该promise对象在iterable参数对象里所有的promise对象都成功的时候才会触发成功,一旦有任何一个iterable里面的promise对象失败则立即触发该promise对象的失败。这个新的promise对象在触发成功状态以后,会把一个包含iterable里所有promise返回值的数组作为成功回调的返回值,顺序跟iterable的顺序保持一致;如果这个新的promise对象触发了失败状态,它会把iterable里第一个触发失败的promise对象的错误信息作为它的失败错误信息。Promise.all方法常被用于处理多个promise对象的状态集合。
- 用法:
<script>
let init = (isMove = true,time = 2,msg) => {
return new Promise((resolve,reject)=> {
let d = setTimeout(()=> {
if(isMove){
resolve(msg);
}else {
reject();
}
},time); // 1000毫秒 = 1秒
})
}
// 初始化3个promise
// init(布尔值,毫秒,字符串)
let p1 = init(true,1000,"执行第一个任务");
let p2 = init(true,2000,"执行第二个任务");
let p3 = init(true,500,"执行第三个任务");
// 利用Promise执行所有的任务
Promise.all([p1,p2,p3]).then(
arr => {
console.log(arr[0]);
console.log(arr[1]);
console.log(arr[2]);
}
)
</script>
- Promise.resolve(value)
- 返回一个状态由给定value决定的Promise对象。如果该值是thenable(即,带有then方法的对象),返回的Promise对象的最终状态由then方法执行决定;否则的话(该value为空,基本类型或者不带then方法的对象),返回的Promise对象状态为fulfilled,并且将该value传递给对应的then方法。通常而言,如果您不知道一个值是否是Promise对象,使用Promise.resolve(value) 来返回一个Promise对象,这样就能将该value以Promise对象形式使用。
- 用法:
Promise.resolve(()=>{
// 可以编写逻辑
}).then(
()=> {
console.log("然后...1");
}
).then(
()=> {
console.log("然后...2");
})
- Promise.reject(reason)
- 返回一个状态为失败的Promise对象,并将给定的失败信息传递给对应的处理方法
- 用法:
Promise.reject(new Error('fail')).then(function() {
// not called
}, function(error) {
console.error(error); // Stacktrace
});
6.详细谈谈js中的异步
1.js单线程分析
- 我们都知道js的一大特点是单线程,也就是同一时间点,只能处理一件事,一句js代码。那为什么js要设计成单线程而不是多线程呢?这主要和js的用途有关,js作为浏览器端的脚本语言,主要的用途为用户与服务端的交互与操作dom。而操作dom就注定了js只能是单线程语言。假如js才取多线程将会出现,多个线程同时对一个dom进行操作的情况,浏览器将无法判断如何渲染。不仅js是单线程,浏览器渲染dom也是单线程的,js的执行和浏览器渲染dom共用的一个线程,这就导致了在html代码中书写js代码会造成浏览器端渲染的阻塞。例如:在html某个位置,写一个段带有alert(‘稍等’),alert 之前html已经被渲染出来,而alert之后的html被这段js阻塞了。为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完 全受主线程控制,且不可进行DOM操作。所以,这个新标准并没有改变JavaScript单线程的本质。
2.异步与同步的理解
- 单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。这就是同步代码阻塞。如果排队是因为计算量大,CPU忙不过来,倒也算了,但是很多时候CPU是闲着的,因为IO设备(输入输出设备)很慢(比如Ajax操作从网络读取数据),不得不等着结果出来,再往下执行。JavaScript语言的设计者意识到,这时主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。
- 简单的说,同步就是会阻塞代码的执行,而异步不会。同样拿alert(‘稍等’) 来举例,在一段js代码中加入一段alert,如果没有点击确认,此时代码的执行就被阻塞了,大多数js代码都是同步执行的。异步则相反。那为什么js中要引入异步的概念呢,很简单,由于js的单线程,当遇到耗时的操作时如果采用同步的执行,那么我们就不可能看到如今这么流畅的web应用了。再举个简单的例子:在一条单行道上行驶着很多汽车,假如其中某一辆车出现机械故障,将会导致后面的车也无法通过,此时应该将故障的车拉入旁边的应急车道进行修复,待它修好之后再重新驶入主干道中,不会影响主干道其它行驶的汽车。所以,异步是js单线程下解决耗时问题的一种“无可奈何”的解决方案。也是一种近乎完美的解决方案。
3.js异步与事件轮询
- 事件轮询(event loop)是js异步的实现方式。
- js事件轮询机制:
- 事件轮询(Event Loop)是一个很重要的概念,指的是计算机系统的一种运行机制。JavaScript语言就是采用的这种机制,来解决单线程运行带来的一些问题。
- 理解事件轮询机制:简单说,就是在程序中设置两个线程:一个负责程序本身的运行,称为"主线程";另一个负责主线程与其他进程(主要是各种I/O操作)的通信,被称为"EventLoop线程"(可以译为"消息线程")。主线程正常一步步运行,每当遇到I/O的时候,主线程就让Event Loop线程去通知相应的I/O程序,然后接着往后运行,所以不存在等待时间。等到I/O程序完成操作,Event Loop线程再把结果返回主线程。主线程就调用事先设定的回调函数,完成整个任务。
- 这种运行方式称为"异步模式"(asynchronous I/O)或"非堵塞模式"(non-blocking mode)。这正是JavaScript语言的运行方式。单线程模型虽然对JavaScript构成了很大的限制,但也因此使它具备了其他语言不具备的优势。如果部署得好,JavaScript程序是不会出现堵塞的,这就是为什么node.js平台可以用很少的资源,应付大流量访问的原因。
版权声明:本文为m0_59593735原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。