ES6 Promise

来源:http://www.chinese-glasses.com 作者:Web前端 人气:96 发布时间:2020-03-24
摘要:时间: 2019-11-02阅读: 105标签: Promise引语 1.含义:是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合理和更强大。 最近一段时间在重温ES6,Promise应该是是ES6新特性中

时间: 2019-11-02阅读: 105标签: Promise引语

1.含义:是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合理和更强大。

最近一段时间在重温ES6,Promise应该是是ES6新特性中非常重要的一部分内容。其实Promise在我日常开发中已经用得比较多,但大多数时候只是知道Promise可以用来实现异步编程,也只限于单纯地会用罢了,并没有时间深入去学习过,而且网上得资料大多都比较琐碎。我就自己花时间做了一个关于Promise比较完整的整理,深入学习一下Promise,加深印象。

2.特点

为什么需要Promise

    (1)对象的状态不受外界影响。promise对象代表一个异步操作,有三总状态,pending(进行中),fulfilled(已成功),rejected(失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态,

JavaScript 由于某种原因是被设计为单线程的,同时由于 JavaScript 在设计之初是用于浏览器的 GUI 编程,这也就需要线程不能进行阻塞。

    (2)一旦状态改变,就不会再变,任何时候都可以得到这个结果,promise对象的状态的改变,只有两种可能:从pending到fulfilled和从pending到rejected。只要这两种状态发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为resolved(已定型)。如果状态已经发生了,你再对promise对象添加回调函数,也会立即得到这个结果。这与事件(event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

所以在后续的发展过程中基本都采用异步非阻塞的编程模式。

注: 后文中 resolved 一律代表fulfilled(已成功)状态

简单来说,异步编程就是在执行一个指令之后不是马上得到结果,而是继续执行后面的指令,等到特定的事件触发后,才得到结果。

有了promise对象,就可以将异步操作的流程表达出来,避免了层层嵌套的回调函数。此外,promise对象提供统一的接口,使得控制异步操作更加容易。

也正是因为这样,我们常常会说: JavaScript 是由事件驱动的。

promise 也有一些缺点,首先,无法取消promise,一旦新建它就会立即执行,无法中途取消,其次,如果不设置回调函数,promise内部抛出的错误,不会反应到外部,第三处于pending状态时,无法得知目前进展到哪一个阶段(刚开始还是快完成)。

用 JavaScript 构建一个应用的时候经常会遇到异步编程,不管是 Node 服务端还是 Web 前端。

基本用法

1,es6规定,promise对象是一个构造函数,用来生成promise实例,promise构造函数接受一个函数作为参数,该函数的两个参数分别为resolve和rejected。他们是两个函数,由javascript引擎提供,不用自己部署。

    resolve函数的作用是,将promise对象的状态从“未完成”变成“已成功”,在异步操作成功时候调用,并将异步操作的结果作为参数传递出去;rejected函数的作用是,将promise的状态从未完成变成失败,在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。promise实例生成以后,可以用then方法分别指定resolved和rejected状态的回调函数,then方法可以接受两个回调函数作为参数,第一个回调函数是promise对象的状态变为resolved时调用,第二个回调函数是promise对象的状态变为rejected时调用,其中第二个函数式可选的,不一定要提供。这两个函数都接受promise对象传出的值作为参数。

简单看个例子

10bet 1

上面代码中,timeout方法返回一个promise实例,表示一段时间以后才会发生的结果。过了制定的时间以后,promise实例的状态变为resolved就会触发then方法绑定的回调函数

插播知识点:定时器启动时候,第三个以后的参数是作为第一个func()的参数传进去。

2,promise新建后就会立即执行。

10bet 2

reject拼写错了,,,

如图执行结果,若是不明白,请看下一章 js的执行机制

3,ajax的用法

10bet 3

上面代码中,getjson是对xmlhttprequest对象的封装,用于发出一个针对json数据的http请求,并且返回一个promise对象,需要注意的是getjson内部,resolve函数和reject函数调用时 都有参数。

4,如果调用resolve和reject函数时候带有参数时,那么他们的参数会被传递给回调函数。reject函数的参数通常是error对象的实例,表示抛出的错误,resolve函数的参数除了正常值以外,还可能是另一个promise实例:

10bet 4

上面代码中,p1和p2都是promise的实例,但是p2的resolve方法将p1作为参数,即一个异步操作的结果是返回另一个异步操作。

注意,这时p1的状态就会传递给p2,也就是说,p1的状态决定了p2的状态,如果p1的状态是pending,那么p2的回调函数就会等待p1的状态改变,如果p1的状态已经是resolve或者reject那么p2的回调函数会立即执行。

5,如图实例

10bet 5

上面代码中,p1是一个promise,3s后变为rejected。p1的状态在1s后改编,resolve返回的方法是p1。由于p2返回的是另一个promise,导致p2自己的状态无效了,由p1的状态决定p2的状态。所以,后面的then语句都变味针对后者(p1)。等过了2秒,p1变为rejected导致触发catch方法指定的回调函数。

6.调用resolve或者reject 并不会终结promise的参数函数的执行

10bet 6

上边的代码中,调用resolve(1)以后,后面的console.log(2)还是会执行,并且会首先打印出来。

一般来说,调用resolve或者reject以后,promise的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolve或者reject后面,所以,最好在他们前面加上return语句,这样就不会有意外。

10bet 7

7,Promise.proyotype.then();

promise有实例方法then,也就是then方法是定义在原型对象Promise.proyotype上的。它的作用是为promise实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是resolved状态的回调函数,第二个(可选)是rejected状态的回调函数。

then方法返回的是一个新的promise实例(不是原来那个promise实例)。因此可以采用链式写法,即then方法后面再调用一个then方法。

10bet 8

上面的代码使用then方法,依次指定了两个回调函数,第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。

采用链式的then,可以指定一组按照次序调用的回调函数。这时,前一个回调函数有可能返回的还是一个promise对象(既有异步操作),这时后一个回调函数,就会等待该promise对象的状态发生变化,才会被调用。

10bet 9

上面代码中,第一个then方法指定的回调函数,返回的是另一个promise对象,第二个then方法指定的回调函数,就会等待这个新的promise对象发生变化,如果变为resolved,就接着调用then的第一个回调函数,如果变为rejected则调用第二个回调函数。

8.Promise.proyotype.catch()

Promise.proyotype.catch方法是then(null,rejection)的别名,用于指定发生错误时的回调函数。

10bet 10

上面代码中,如果该对象的状态返回的是resolved则调用then方法,如果返回rejected则调用catch方法,如果then方法指定的回调函数运行中抛出错误,也会被catch捕获。

如果promise的状态已经变为resolved 则再抛出错误时无效的

10bet 11

上面代码中,promise在resolve语句的后面,再抛出错误,不会被捕获,等于没有被抛出,因为promise状态一旦改变,就永久保持该状态,不会再变了。

promise对象的错误信息,具有冒泡性质,会一直向后传递,直到被捕获为止。也就是说,错误总是被下一个catch语句捕获。

10bet 12

上边代码中一共有三个promise对象,他们之中任何一个抛出错误,都会被最后一个catch捕获。

一般来说,不要在then里边定义reject状态的回调函数(即then的第二个参数),总是使用catch方法。

一般来说总是建议,promise对象后面要跟catch方法,这样处理promise内部发生的错误,catch方法返回的还是一个promise对象,因此后面还可以接着调用then方法。catch里边抛出错误还可以被下一个catch捕获,如果不写则不会抛出了,如图为捕获catch里的错误

10bet 13

9.Promise.proyotype.finally()

    不管promise对象的状态如何,都会执行的操作

10.Promise.all()

10bet 14

10bet 15

10bet 16

10bet 17

10bet 18

10bet 19

10bet 20

10bet 21

10bet 22

那如何去进行异步编程呢?回调函数就是异步执行最基础的实现。如果你曾经写过一点的 Node, 可能经常会遇到这样的代码:

connection.query(sql, (err, result) = { if(err) { console.err(err) } else { connection.query(sql, (err, result) = { if(err) { console.err(err) } else { … } }) }})

如此,connection.query() 是一个异步的操作,我们在调用他的时候,不会马上得到结果,而是会继续执行后面的代码。这样,如果我们需要在查到结果之后才做某些事情的话,就需要把相关的代码写在回调里面。

这样的代码看层级少了当然还是可以凑合看的,但是在某些场景下,我们就需要一次又一次的回调…回调到最后,会发现我们的代码就会变成金字塔形状?这种情况被亲切地称为回调地狱

回调地狱不仅看起来很不舒服,可读性比较差,难以维护,除此之外还有比较重要的一点就是对异常的捕获无法支持。无论是开发者自身还是同事来接手项目,都是极其无奈的!

有没有更好的写法?比如写成这样的链式调用:

let con = connection.query(…con.success(…) .fail(…)

于是乎,出现了Promise!

什么是Promise?Promise的含义

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。

古人云:“君子一诺千金”,所谓Promise,正如其中文含义,简单说就是一个承诺,“承诺将来会执行”约定的事情。

从语法上说,Promise 是一个对象,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

约定

不同于“老式”的传入回调,在使用 Promise 时,会有以下约定:

在本轮 Javascript event loop(事件循环)运行完成之前,回调函数是不会被调用的。通过then()添加的回调函数总会被调用,即便它是在异步操作完成之后才被添加的函数。通过多次调用then(),可以添加多个回调函数,它们会按照插入顺序一个接一个独立执行。

因此,Promise 最直接的好处就是链式调用chaining)。

Promise的使用一个Promise的三种状态

在开始使用Promise之前,我们首先需要了解Promise的三种状态:

pending: 初始状态,既不是成功,也不是失败状态。fulfilled: 意味着操作成功完成。rejected: 意味着操作失败。

pending 状态的 Promise 对象可能会变为fulfilled 状态并传递一个值给相应的状态处理方法,也可能变为失败状态(rejected)并传递失败信息。当其中任一种情况出现时,Promise 对象的 then 方法绑定的处理方法(handlers )就会被调用(then方法包含两个参数:onfulfilled 和 onrejected,它们都是 Function 类型。当Promise状态为fulfilled时,调用 then 的 onfulfilled 方法,当Promise状态为rejected时,调用 then 的 onrejected 方法, 所以在异步操作的完成和绑定处理方法之间不存在竞争)。

因为Promise.prototype.thenPromise.prototype.catch方法返回promise 对象, 所以它们可以被链式调用

Promise的这三种状态有两个特点:

对象的状态不受外界影响。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。Promise基本用法

ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。

下面代码创造了一个Promise实例。

const promise = new Promise(function(resolve, reject) { // … some code If (/* 异步操作成功 */){ resolve(value); } else { reject(error); }});

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。

Resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;

Reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。

promise.then(function(value) { // success}, function(error) { // failure});

then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。

下面是一个Promise对象的简单例子。

function timeout(ms) { return new Promise((resolve, reject) = { setTimeout(resolve, ms, 'done'); });}timeout(100).then((value) = { console.log(value);});

上面代码中,timeout方法返回一个Promise实例,表示一段时间以后才会发生的结果。过了指定的时间(ms参数)以后,Promise实例的状态变为resolved,就会触发then方法绑定的回调函数。

Promise 新建后就会立即执行

let promise = new Promise(function(resolve, reject) { console.log('1'); resolve();});promise.then(function() { console.log('2');});console.log('3');// = 1// = 3// = 2

上面代码中,Promise 新建后立即执行,所以首先输出的是1。然后,then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以2最后输出。

注意,调用resolve或reject并不会终结 Promise 的参数函数的执行。

new Promise((resolve, reject) = { resolve(1); console.log(2);}).then(r = { console.log(r);});// 2// 1

上面代码中,调用resolve(1)以后,后面的console.log(2)还是会执行,并且会首先打印出来。这是因为立即 resolved 的 Promise 是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。

一般来说,调用resolve或reject以后,Promise 的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolve或reject的后面。所以,最好在它们前面加上return语句,这样就不会有意外。

new Promise((resolve, reject) = { return resolve(1); // 后面的语句不会执行 console.log(2);})

下面是异步加载图片的例子。

function loadImageAsync(url) { return new Promise(function(resolve, reject) { const image = new Image(); image.onload = function() { resolve(image); }; image.onerror = function() { reject(new Error('Could not load image at ' + url)); }; image.src = url; });}

上面代码中,使用Promise包装了一个图片加载的异步操作。如果加载成功,就调用resolve方法,否则就调用reject方法。

下面是一个用Promise对象实现的 Ajax 操作的例子。

const getJSON = function(url) { const promise = new Promise(function(resolve, reject){ const handler = function() { if (this.readyState !== 4) { return; } if (this.status === 200) { resolve(this.response); } else { reject(new Error(this.statusText)); } }; const client = new XMLHttpRequest(); client.open("GET", url); client.onreadystatechange = handler; client.responseType = "json"; client.setRequestHeader("Accept", "application/json"); client.send(); }); return promise;};getJSON("/posts.json").then(function(json) { console.log('Contents: ' + json);}, function(error) { console.error('出错了', error);});

上面代码中,getJSON是对 XMLHttpRequest 对象的封装,用于发出一个针对 JSON 数据的 HTTP 请求,并且返回一个Promise对象。需要注意的是,在getJSON内部,resolve函数和reject函数调用时,都带有参数。

深入Promise原型

对于Promise的基本用法有了一定认识之后,我们来深入学习一下Promise原型方法。

Promise.prototype.then(onFulfilled, onRejected)

上面的例子中已经提到,Promise 实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的。它的作用是为 Promise 实例添加状态改变时的回调函数。then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。

链式调用

连续执行两个或者多个异步操作是一个常见的需求,在上一个操作执行成功之后,开始下一个的操作,并带着上一步操作所返回的结果。我们可以通过创造一个Promise 链来实现这种需求。

then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式调用写法,即then方法后面再调用另一个then方法。

const promise = doSomething();const promise2 = promise.then(successCallback, failureCallback);

或者

constpromise2 = doSomething().then(successCallback, failureCallback);

第二个对象(promise2)不仅表示 doSomething() 函数的完成,也代表了你传入的 successCallback 或者 failureCallback 的完成,successCallback 或 failureCallback 有可能返回一个 Promise 对象,从而形成另一个异步操作。这样的话,任何一个 promise2 新增的回调函数,都会被依次排在由上一个successCallback 或 failureCallback 执行后所返回的 Promise 对象的后面。

基本上,每一个 Promise 都代表了链中另一个异步过程的完成。

正如文章开头提到过的,在过去,要想做多重的异步操作,会导致经典的回调地狱:

doSomething(function(result) { doSomethingElse(result, function(newResult) { doThirdThing(newResult, function(finalResult) { console.log('Got the final result: ' + finalResult); }, failureCallback); }, failureCallback);}, failureCallback);

通过新的功能方法,我们把回调绑定到被返回的 Promise 上代替以往的做法,形成一个 Promise 链:

doSomething().then(function(result) { return doSomethingElse(result);}).then(function(newResult) { return doThirdThing(newResult);}).then(function(finalResult) { console.log('Got the final result: ' + finalResult);}).catch(failureCallback);

then里的参数是可选的,如下所示,我们也可以用箭头函数来表示:

doSomething().then(result = doSomethingElse(result)).then(newResult = doThirdThing(newResult)).then(finalResult = { console.log(`Got the final result: ${finalResult}`);}).catch(failureCallback);

注意:一定要有返回值,否则,callback 将无法获取上一个 Promise 的结果。(如果使用箭头函数,() = x 比 () = { return x; } 更简洁一些,但后一种保留 return 的写法才支持使用多个语句。)。

Promise.prototype.catch(onRejected)

添加一个拒绝(rejection) 回调到当前 promise, 返回一个新的promise。当这个回调函数被调用,新 promise 将以它的返回值来resolve,否则如果当前promise 进入fulfilled状态,则以当前promise的完成结果作为新promise的完成结果。

Promise.prototype.catch方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。

getJSON('/posts.json').then(function(posts) { // …}).catch(function(error) { // 处理 getJSON 和 前一个回调函数运行时发生的错误 console.log('发生错误!', error);});

上面代码中,getJSON方法返回一个 Promise 对象,如果该对象状态变为resolved,则会调用then方法指定的回调函数;如果异步操作抛出错误,状态就会变为rejected,就会调用catch方法指定的回调函数,处理这个错误。另外,then方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获。

p.then((val) = console.log('fulfilled:', val)) .catch((err) = console.log('rejected', err));// 等同于p.then((val) = console.log('fulfilled:', val)) .then(null, (err) = console.log("rejected:", err));

下面是一个例子。

const promise = new Promise(function(resolve, reject) { throw new Error('test');});promise.catch(function(error) { console.log(error);});// Error: test

上面代码中,promise抛出一个错误,就被catch方法指定的回调函数捕获。注意,上面的写法与下面两种写法是等价的。

// 写法一const promise = new Promise(function(resolve, reject) { try { throw new Error('test'); } catch(e) { reject(e); }});promise.catch(function(error) { console.log(error);});

// 写法二const promise = new Promise(function(resolve, reject) { reject(new Error('test'));});promise.catch(function(error) { console.log(error);});

比较上面两种写法,可以发现reject方法的作用,等同于抛出错误。

如果 Promise 状态已经变成resolved,再抛出错误是无效的。

const promise = new Promise(function(resolve, reject) { resolve('ok'); throw new Error('test');});promise .then(function(value) { console.log(value) }) .catch(function(error) { console.log(error) });// ok

上面代码中,Promise 在resolve语句后面,再抛出错误,不会被捕获,等于没有抛出。因为 Promise 的状态一旦改变,就永久保持该状态,不会再变了。

Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。

getJSON('/post/1.json').then(function(post) { return getJSON(post.commentURL);}).then(function(comments) { // some code}).catch(function(error) { // 处理前面三个Promise产生的错误});

上面代码中,一共有三个 Promise 对象:一个由getJSON产生,两个由then产生。它们之中任何一个抛出的错误,都会被最后一个catch捕获。

一般来说,不要在then方法里面定义 Reject 状态的回调函数(即then的第二个参数),总是使用catch方法。

// badpromise .then(function(data) { // success }, function(err) { // error });// goodpromise .then(function(data) { //cb // success }) .catch(function(err) { // error });

上面代码中,第二种写法要好于第一种写法,理由是第二种写法可以捕获前面then方法执行中的错误,也更接近同步的写法(try/catch)。因此,建议总是使用catch方法,而不使用then方法的第二个参数。

跟传统的try/catch代码块不同的是,如果没有使用catch方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。

const someAsyncThing = function() { return new Promise(function(resolve, reject) { // 下面一行会报错,因为x没有声明 resolve(x + 2); });};someAsyncThing().then(function() { console.log('everything is great');});setTimeout(() = { console.log(123) }, 2000);// Uncaught (in promise) ReferenceError: x is not defined// 123

上面代码中,someAsyncThing函数产生的 Promise 对象,内部有语法错误。浏览器运行到这一行,会打印出错误提示ReferenceError: x is not defined,但是不会退出进程、终止脚本执行,2 秒之后还是会输出123。这就是说,Promise 内部的错误不会影响到 Promise 外部的代码,通俗的说法就是“Promise 会吃掉错误”。

这个脚本放在服务器执行,退出码就是0(即表示执行成功)。不过,Node 有一个unhandledRejection事件,专门监听未捕获的reject错误,上面的脚本会触发这个事件的监听函数,可以在监听函数里面抛出错误。

process.on('unhandledRejection', function (err, p) { throw err;});

上面代码中,unhandledRejection事件的监听函数有两个参数,第一个是错误对象,第二个是报错的 Promise 实例,它可以用来了解发生错误的环境信息。

注意,Node 有计划在未来废除unhandledRejection事件。如果 Promise 内部有未捕获的错误,会直接终止进程,并且进程的退出码不为 0。

再看下面的例子。

const promise = new Promise(function (resolve, reject) { resolve('ok'); setTimeout(function () { throw new Error('test') }, 0)});promise.then(function (value) { console.log(value) });// ok// Uncaught Error: test

上面代码中,Promise 指定在下一轮“事件循环”再抛出错误。到了那个时候,Promise 的运行已经结束了,所以这个错误是在 Promise 函数体外抛出的,会冒泡到最外层,成了未捕获的错误。

10bet,一般总是建议,Promise 对象后面要跟catch方法,这样可以处理 Promise 内部发生的错误。catch方法返回的还是一个 Promise 对象,因此后面还可以接着调用then方法。

const someAsyncThing = function() { return new Promise(function(resolve, reject) { // 下面一行会报错,因为x没有声明 resolve(x + 2); });};someAsyncThing().catch(function(error) { console.log('oh no', error);}).then(function() { console.log('carry on');});// oh no [ReferenceError: x is not defined]// carry on

上面代码运行完catch方法指定的回调函数,会接着运行后面那个then方法指定的回调函数。如果没有报错,则会跳过catch方法。

Promise.resolve().catch(function(error) { console.log('oh no', error);}).then(function() { console.log('carry on');});// carry on

上面的代码因为没有报错,跳过了catch方法,直接执行后面的then方法。此时,要是then方法里面报错,就与前面的catch无关了。Catch方法之中,有可能会在一个回调失败之后继续使用链式操作,即使用一个 catch,这对于在链式操作中抛出一个失败之后,再次进行新的操作很有用。

const someAsyncThing = function() { return new Promise(function(resolve, reject) { // 下面一行会报错,因为x没有声明 resolve(x + 2); });};someAsyncThing().then(function() { return someOtherAsyncThing();}).catch(function(error) { console.log('oh no', error); // 抛出一个错误 throw new Error('有哪里不对了'); console.log('carry on');});// oh no [ReferenceError: x is not defined]

上面代码中,catch方法抛出一个错误,因为后面没有别的catch方法了,导致这个错误不会被捕获,也不会传递到外层。如果改写一下,结果就不一样了。

someAsyncThing().then(function() { return someOtherAsyncThing();}).catch(function(error) { console.log('oh no', error); // 抛出一个错误 throw new Error('有哪里不对了'); console.log('carry on');}).catch(function(error) { console.log('carry on', error);});// oh no [ReferenceError: x is not defined]// carry on 有哪里不对了

上面代码中,第二个catch方法用来捕获前一个catch方法抛出的错误。

也许你发现了,最后~carry on~并没有被输出,这是因为抛出了错误 ~有哪里不对了~

在之前的回调地狱示例中,你可能记得有 3 次 failureCallback 的调用,而在 Promise 链中只有尾部的一次调用。

doSomething().then(result = doSomethingElse(value)).then(newResult = doThirdThing(newResult)).then(finalResult = console.log(`Got the final result: ${finalResult}`)).catch(failureCallback);

通常,一遇到异常抛出,Promise 链就会停下来,直接调用链式中的 catch 处理程序来继续当前执行。这看起来和以下的同步代码的执行很相似。

try { let result = syncDoSomething(); let newResult = syncDoSomethingElse(result); let finalResult = syncDoThirdThing(newResult); console.log(`Got the final result: ${finalResult}`);} catch(error) { failureCallback(error);}

在 ECMAScript 2017 标准的async/await语法糖中,这种同步形式代码的对称性得到了极致的体现:

async function foo() { try { let result = await doSomething(); let newResult = await doSomethingElse(result); let finalResult = await doThirdThing(newResult); console.log(`Got the final result: ${finalResult}`); } catch(error) { failureCallback(error); }}

这个例子是在 Promise 的基础上构建的,例如,doSomething() 与之前的函数是相同的。

通过捕获所有的错误,甚至抛出异常和程序错误,Promise 解决了回调地狱的基本缺陷。这对于构建异步操作的基础功能而言是很有必要的。

Promise.prototype.finally(onFinally)

添加一个事件处理回调于当前promise对象,并且在原promise对象解析完毕后,返回一个新的promise对象。回调会在当前promise运行完毕后被调用,无论当前promise的状态是完成(fulfilled)还是失败(rejected)。该方法是 ES2018 引入标准的。

promise.then(result = {···}).catch(error = {···}).finally(() = {···});

本文由10bet发布于Web前端,转载请注明出处:ES6 Promise

关键词:

频道精选

最火资讯