当我们不知道函数的返回值或返回需要多长时间时,使用Promise是构建异步应用程序的好方法。它们使无需深层嵌套的回调即可更轻松地表达和推理异步操作的序列,并且它们支持类似于同步try...catch语句的错误处理方式。承诺适用于所有现代浏览器的最新版本
Promise定义
什么是Promise?
从本质上讲,Promise是表示操作中间状态的对象,实际上,它是将来会在某个时候返回某种结果的保证。不能保证确切地知道何时该操作将完成并返回结果,但是可以保证当结果可用或诺言失败时,将执行您提供的代码,以便用成功的结果,或妥善处理失败案例。
一般情况下,你是不感兴趣的时间异步操作将返回其结果量(当然,除非它需要远远太长!),更感兴趣的是能够响应它返回,只要那是。当然,它不会阻塞其余的代码执行也很好。
举例一个视频聊天应用程序
该功能通过使用一个名为setStatusMessage()“ Calling …”的消息来更新状态显示的函数来开始,该消息表示正在尝试进行呼叫。然后getUserMedia(),它呼叫,要求同时具有视频和音频轨道的流,然后获取该流,然后设置视频元素以将来自摄像机的流显示为“自视图”,然后获取该流的每个轨道并将它们添加到WebRTC, RTCPeerConnection代表与另一个用户的连接。之后,状态显示更新为“已连接”。
如果getUserMedia()失败,则该catch块运行。这用于setStatusMessage()更新状态框以指示发生了错误。
这里重要的是getUserMedia(),即使尚未获取摄像机流,呼叫也几乎立即返回。即使handleCallButton()函数已经返回调用它的代码,当getUserMedia()工作完成时,它也会调用您提供的处理程序。只要应用程序不认为流媒体已经开始,它就可以继续运行。
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");
});
}
解释并实践Promise
使用该方法从Web上获取图像,该blob()方法将获取响应的原始内容转换为Blob对象,然后在<img>
元素内部显示该Blob 。
调用该fetch()方法,并将要传递给网络的图像URL作为参数传递给该方法。这也可以将options对象作为可选的第二个参数,但是我们现在仅使用最简单的版本。我们将存储在fetch()称为的变量内部返回的Promise对象promise
let promise = fetch('coffee.jpg');
为了响应成功完成的操作(在这种情况下,当Response返回a时),我们调用.then()promise对象的方法。内部的回调.then()块(简称执行人),只有当承诺调用成功完成,并返回运行Response中的承诺发言,当它已经-对象实现。将返回的Response对象作为参数传递给它。
注意:.then()块的工作方式类似于使用来将事件侦听器添加到对象时AddEventListener()。直到事件发生时(诺言履行时),它才会运行。最显着的区别是,.then()每次使用a时,它将只运行一次,而事件侦听器可以多次调用。
我们立即在此响应上运行该方法,以确保完整下载了响应主体,并在可用时将其转换为Blob可以处理的对象。这样返回的结果如下:
response => response.blob()
function(response) {
return response.blob();
}
提取承诺不会因404或500错误而失败-只会在诸如网络故障之类的灾难性事件上失败。相反,它们成功了,但response.ok属性设置为false。例如,要在404上产生错误,我们需要检查的值response.ok,如果false抛出错误,则仅返回blob(如果是)true。可以这样做-在JavaScript的第一行下面添加以下行。
let promise2 = promise.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
} else {
return response.blob();
}
});
每次致电.then()都会创建一个新的承诺。这非常有用;因为该方法还返回了一个Promise,所以我们可以通过调用.then()第二个Promise的方法来处理在实现时返回的对象。因为我们要对blob进行一些操作,而不是对它单独运行一个简单的方法并返回结果,所以这次我们需要将函数体用大括号括起来(否则会抛出错误)。
在代码末尾添加以下内容:
let promise3 = promise2.then(myBlob => {
})
正在运行URL.createObjectURL()方法,将其作为参数传递Blob给第二个Promise满足时返回。这将返回指向该对象的URL。然后,我们创建一个<img>
元素,将其src属性设置为等于对象URL并将其附加到DOM,这样图像就会显示在页面上
填写执行程序函数的主体,在花括号内添加以下行:
let objectURL = URL.createObjectURL(myBlob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
保存刚创建的HTML文件并将其加载到浏览器中,则会看到图像按预期显示在页面中。
应对失败
缺少某些内容-当前,如果其中一个诺言失败(拒绝,以诺言),则没有任何东西可以显式处理错误。我们可以通过.catch()根据先前的承诺运行方法来添加错误处理
要查看实际效果,请尝试将图像的URL拼写错误并重新加载页面。该错误将在浏览器开发人员工具的控制台中报告。
let errorCase = promise3.catch(e => {
console.log('There has been a problem with your fetch operation: ' + e.message);
});
链接块
将.then()块(以及.catch()块)链接在一起,帮助您清楚地了解正在发生的事情
fetch('coffee.jpg')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
} else {
return response.blob();
}
})
.then(myBlob => {
let objectURL = URL.createObjectURL(myBlob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
})
.catch(e => {
console.log('There has been a problem with your fetch operation: ' + e.message);
});
创建自己的Promise
使用Promise()构造函数
可以使用Promise()构造函数来构建自己的Promise 。您要执行此操作的主要情况是,您希望基于承诺不基于承诺的老式异步API编写代码。当您需要使用现有的,较旧的项目代码,库或框架以及现代的基于诺言的代码时,这非常方便。
resolve()并且reject()是你打电话履行或拒绝新创建的诺言功能。在这种情况下,promise将使用字符串“ Success!”来实现。
let timeoutPromise = new Promise((resolve, reject) => {
setTimeout(function(){
resolve('Success!');
}, 2000);
});
因此,当您调用此Promise时,您可以将一个.then()块链接到它的末尾,并将其传递给字符串“ Success!”。在下面的代码中,我们只是警告该消息:
timeoutPromise
.then((message) => {
alert(message);
})
甚至只是
timeoutPromise.then(alert);
拒绝自定义承诺
可以使用该reject()方法创建一个拒绝的承诺—就像resolve(),它采用单个值,但是在这种情况下,这是拒绝的原因,即,错误将传递到.catch()块中。
让我们扩展前面的示例,使其具有一些reject()条件,并允许在成功时传递不同的消息。
将两个参数传递给自定义函数-执行某项操作的消息以及执行该操作之前要经过的时间间隔。然后在函数内部,我们返回一个新Promise对象-调用该函数将返回我们要使用的承诺
function timeoutPromise(message, interval) {
return new Promise((resolve, reject) => {
if (message === '' || typeof message !== 'string') {
reject('Message is empty or not a string');
} else if (interval < 0 || typeof interval !== 'number') {
reject('Interval is negative or not a number');
} else {
setTimeout(function(){
resolve(message);
}, interval);
}
});
};
在Promise构造函数内部,我们在if … else结构内部进行了一些检查:
首先,我们检查消息是否适合被警报。如果它是一个空字符串或根本不是一个字符串,我们会以适当的错误消息拒绝promise。
接下来,我们检查间隔是否为适当的间隔值。如果它是负数或不是数字,我们会以适当的错误消息拒绝诺言。
最后,如果两个参数都看起来都不错,则在使用指定的时间间隔后,我们将使用指定的消息来解决诺言setTimeout()。
由于该timeoutPromise()函数返回Promise,我们可以链.then(),.catch()等到它要利用它的功能。现在使用它- timeoutPromise用此替换以前的用法:
timeoutPromise('Hello there!', 1000)
.then(message => {
alert(message);
})
.catch(e => {
console.log('Error: ' + e);