简单介绍一下 Promise 以及他的使用、异常处理、同步处理等等…
介绍 我们都知道 JavaScript 是一种同步编程语言,上一行出错就会影响下一行的执行,但是我们需要数据的时候总不能每次都等上一行执行完成,这时就可以使用回调函数让它像异步编程语言一样工作。 像 NodeJS 就是采用异步回调的方式来处理需要等待的事件,使得代码会继续往下执行不用在某个地方等待着。但是也有一个不好的地方,当我们有很多回调的时候,比如这个回调执行完需要去执行下个回调,然后接着再执行下个回调,这样就会造成层层嵌套,代码不清晰,很容易进入“回调监狱” 。。。 所以 ES6 新出的 Promise
对象以及 ES7 的 async、await
都可以解决这个问题。 Promise 是用来处理异步操作的,可以让我们写异步调用的时候写起来更加优雅,更加美观便于阅读。Promise 为承诺的意思,意思是使用 Promise 之后他肯定会给我们答复,无论成功或者失败都会给我们一个答复,所以我们就不用担心他跑了哈哈。 Promise 有三种状态:pending (未决定),resolved (完成fulfilled
),rejected (失败)。只有异步返回时才可以改变其状态,因此我们收到的 Promise 过程状态一般只有两种:pending->fulfilled
或者 pending->rejected
。
使用 简单使用 直接上代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 function promiseTest (boolType = true ) { return new Promise (function (resolve, reject ) { if (boolType) { resolve ('成功' ); } else { reject ('失败' ); } }); } promiseTest (true ).then ((value ) => console .log (`${value} 后的处理A` ));promiseTest (false ).then ( (value ) => console .log (`${value} 后的处理B` ), (value ) => console .log (`${value} 后的处理B` ) ); promiseTest (false ).catch ((value ) => console .log (`${value} 后的处理C` ));promiseTest (false ) .catch ((value ) => promiseTest (true )) .then (() => console .log ('第一次调用失败后尝试第二次成功了!' )); promiseTest (true ) .then ((value ) => value1) .catch ((e ) => console .log (e)); 成功后的处理A 失败后的处理B 失败后的处理C 第一次调用失败后尝试第二次成功了! ReferenceError : value1 is not defined at ...
另外当我们需要在方法中等待 Promise
返回时,需要给方法添加 async
修饰,并使用 await
等待。
1 2 3 4 5 6 7 8 9 async function asyncFunc ( ) { let result = await new Promise ((resolve, reject ) => { setTimeout (() => resolve (123 ), 2000 ); }); return result; } asyncFunc (); asyncFunc ().then ((value ) => console .log (value)); await asyncFunc ();
Api 方法 Promise.resolve 将现有对象转为 Promise 对象 resolved,Promise.resolve(‘test’) 相当于 new Promise((resolve) => resolve(‘test’));
Promise.reject 将现有对象转为 Promise 对象 rejected,Promise.rejected(‘test’) 相当于 new Promise((rejected) => rejected(‘test’));
Promise.prototype.then then() 方法返回一个 Promise,它最多需要有两个参数:Promise 的成功和失败情况的回调函数。
1 2 3 4 5 6 promiseTest.then (value => { }, reason => { });
onFulfilled 可选当 Promise 变成接受状态(fulfilled)时调用的函数。该函数有一个参数,即接受的最终结果(the fulfillment value)。 如果该参数不是函数,则会在内部被替换为 (x) => x,即原样返回 promise 最终结果的函数。 onRejected 可选当 Promise 变成拒绝状态(rejected)时调用的函数。该函数有一个参数,即拒绝的原因(rejection reason)。 如果该参数不是函数,则会在内部被替换为一个 “Thrower” 函数 (it throws an error it received as argument)。 Promise.prototype.catch catch() 方法返回一个 Promise,并且处理拒绝的情况。它的行为与调用 Promise.prototype.then(undefined,onRejected)
相同。事实上调用 obj.catch(onRejected)
其实就是 obj.then(undefined, onRejected)
。
1 2 3 4 promiseTest.catch (function (reason ) { });
onRejected当 Promise 被 rejected 时,被调用的一个Function。该函数拥有一个参数:reason/rejection 的原因。 如果 onRejected 抛出一个错误或返回一个本身失败的 Promise,通过 catch() 返回的 Promise 被 rejected。否则,它将显示为成功(resolved)。 Promise.prototype.finally finally() 方法返回一个 Promise。在 Promise 结束时,无论结果是 fulfilled 或者是 rejected,都会执行指定的回调函数。
这为在 Promise 是否成功完成后都需要执行的代码提供了一种方式。这避免了同样的语句需要在 then()
和 catch()
中 各写一次 的情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 promiseTest.finally (() => { }); promiseTest.then ( (result ) => { return result; }, (error ) => { throw error; } );
Promise.allSettled 该 Promise.allSettled() 方法返回一个在所有给定的 Promise 都已经 fulfilled 或 rejected 后的 Promise
,并带有一个对象数组,每个对象表示对应的 Promise 结果。
1 2 3 4 5 6 7 8 9 const promise1 = Promise .resolve (3 );const promise2 = new Promise ((resolve, reject ) => setTimeout (() => reject ('test' ), 1000 ));const promises = [promise1, promise2];Promise .allSettled (promises).then ((results ) => results.forEach ((result ) => console .log (result.status )));fulfilled rejected
Promise.all Promise.all() 方法接收一个 Promise 的 iterable 类型(Array,Map,Set都属于 ES6 的 iterable 类型)
的输入,并且只返回一个 Promise 实例,那个输入的所有 Promise 的 resolve 回调的结果是一个数组。 它的 resolve
回调执行是在所有输入的 Promise 的 resolve 回调都结束,或者输入的 iterable 里没有 Promise 了的时候。 它的 reject
回调执行是只要任何一个输入的 Promise 的 reject 回调执行或者输入不合法的 Promise 就会立即抛出错误,并且 reject 的是第一个抛出的错误信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 console .time ('不使用Promise.all' );let a = await new Promise ((resolve, reject ) => { setTimeout (function ( ) { resolve ('123' ); }, 1000 ); }); let b = await new Promise ((resolve, reject ) => { setTimeout (function ( ) { resolve ('456' ); }, 2000 ); }); let c = await new Promise ((resolve, reject ) => { setTimeout (function ( ) { resolve ('789' ); }, 3000 ); }); console .log (a, b, c);console .timeEnd ('不使用Promise.all' );console .time ('使用Promise.all' );function all ( ) { return Promise .all ([ new Promise ((resolve, reject ) => { setTimeout (function ( ) { resolve ('123' ); }, 1000 ); }), new Promise ((resolve, reject ) => { setTimeout (function ( ) { resolve ('456' ); }, 2000 ); }), new Promise ((resolve, reject ) => { setTimeout (function ( ) { resolve ('789' ); }, 3000 ); }) ]); } console .log (...(await all ()));console .timeEnd ('使用Promise.all' );123 456 789 不使用Promise .all : 8569.14794921875 ms 123 456 789 使用Promise .all : 3006.345947265625 ms
我们可以看到,不使用 all 的情况下我们需要等待的时间会长很多,而使用 all 之后,我们的请求相当于并发,大大节约了时间。 Promise.race Promise.race(iterable) 方法返回一个 Promise,一旦迭代器中的某个 Promise 解决或拒绝,返回的 Promise 就会解决或拒绝。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 function race ( ) { return Promise .race ([ new Promise ((resolve, reject ) => { setTimeout (function ( ) { resolve ('123' ); }, 1000 ); }), new Promise ((resolve, reject ) => { setTimeout (function ( ) { resolve ('456' ); }, 2000 ); }), new Promise ((resolve, reject ) => { setTimeout (function ( ) { resolve ('789' ); }, 3000 ); }) ]); } console .time ('raceTime' );console .log (await race ());console .timeEnd ('raceTime' );123 raceTime : 1056.11083984375 ms
Promise.any Promise.any() 接收一个 Promise 可迭代对象,只要其中的一个 Promise 成功,就返回那个已经成功的 Promise。如果可迭代对象中没有一个 Promise 成功 (即所有的 Promise 都失败/拒绝) ,就返回一个失败的 Promise 和 AggregateError 类型的实例
,它是 Error 的一个子类,用于把单一的错误集合在一起 。本质上,这个方法和 Promise.all() 是相反的。
注意:Promise.any() 方法依然是实验性的,尚未被所有的浏览器完全支持。它当前处于 TC39 第四阶段草案。
Promise.any() 与 Promise.race() 方法不同,Promise.race()
方法主要关注 Promise 是否已解决
,而不管其被解决(成功)还是被拒绝(失败)
。所以使用 Promise.any 来获取多台服务器数据时会更合理。
优雅的进行异常处理 详解 之前刷视频有看到一些小问题:使用多个 await 时,前一个出现异常,如何不影响后续执行? 我们每次使用 Promise 都需要处理异常吗? 如何统一处理异常和捕获异步异常呢? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 function test1 ( ) { return new Promise ((resolve, reject ) => { setTimeout (function ( ) { console .log ('test1' ); resolve ('test1' ); }, 1000 ); }); } function test2 ( ) { return new Promise ((resolve, reject ) => { var x = abc + 1 ; console .log ('test2' ); resolve ('test2' ); }); } function test3 ( ) { return new Promise ((resolve, reject ) => { setTimeout (function ( ) { try { var y = abcabc + 1 ; resolve (y); } catch (e) { console .log ('不属于 Promise 内部错误,请自己包裹。' ); console .log ('不包裹则会冒泡到 window.onerror,若再未处理则报错到控制台。示例:test4!' ); reject ('test3 error' ); } }, 1000 ); }); } function test4 ( ) { return new Promise ((resolve, reject ) => { setTimeout (function ( ) { var z = abcabcabc + 1 ; console .log (z); }, 1000 ); reject ('test4 error' ); }); }
首先我们看第一个问题,如果我们直接这样执行,那么由于 test2()
出现错误,test1()
肯定是无法执行的。 1 2 await test2 ();await test1 ();
这时候我们需要这样写,但是这样虽然可以解决这个问题,但是如果前面的 Promise 数量一多,那么可读性就大大降低了! 1 2 3 4 5 6 7 8 9 await test2 ().catch ((e ) => console .log (e));await test1 ();或 try { await test2 (); } catch (e) { console .log (e); } await test1 ();
再结合后面两个问题,我查看了一些资料,包括 Dima Grossman 的 to.js ,所以我们可以采用终极方案,话不多说直接上代码。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 Promise .prototype .to = function (res, rej ) { return this .then ((data ) => { res && res (data); return data; }).catch ((err ) => { rej && rej (err); console .log (err); }); }; window .onerror = function (message, source, lineno, colno, error ) { console .log ('捕获到异常:' , { message, source, lineno, colno, error }); return true ; };
此时我们再如此执行,均不会报错。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 await test1 ();await test2 ();await test3 ();await test4 ();console .log ('前面报错不会执行' );test1 ();test2 ();test3 ();test4 ();console .log ('前面报错不会执行' );await test1 ().to ();await test2 ().to ();await test3 ().to ();await test4 ().to ();console .log ('前面报错依然会执行' );test1 ().to ((x ) => console .log (`自定义处理的${x} ` )); test2 ().to ();test3 ().to ();test4 ().to (); console .log ('前面报错依然会执行' );
多说几句 另外补充一下,说到 Promise 的优雅处理,我们平时写的时候前往不要像下面一样嵌套使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 function request1 ( ) { return new Promise (function (resolve, reject ) { setTimeout (function ( ) { resolve ('result1' ); }, 1000 ); }); } function request2 (need1 ) { return new Promise (function (resolve, reject ) { setTimeout (function ( ) { resolve (need1 + 'result2' ); }, 1000 ); }); } function request3 (need2 ) { return new Promise (function (resolve, reject ) { setTimeout (function ( ) { resolve (need2 + 'result3' ); }, 1000 ); }); } request1 ().then ((res1 ) => { request2 (res1).then ((res2 ) => { request3 (res2).then ((res3 ) => { console .log (res3); }); }); });
而应该是每次调用 then 方法后,在 then 方法中 return 下一次需要用到的数据 。然后 then 方法会返回一个 Promise 实例,再继续使用 then 通过 res 参数可以获取上一次 return 的数据 ,并在该 then 方法中发送后续的异步请求,这样就达到了我们之前说过的链式调用传递效果,而且 reject 抛出错误的时候,只需在最后 catch 一层就可以了 ,这样无论是哪个 then reject 了,都会在最后的 catch 这里捕获到错误 。
1 2 3 4 5 6 request1 () .then ((res1 ) => request2 (res1)) .then ((res2 ) => request3 (res2)) .then ((res3 ) => console .log (res3)) .catch ((e ) => console .log ('异常处理' , e));
实现 Promise Retry 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Promise .prototype .retry = function (count = 0 , delay = 0 ) { return new Promise ((resolve, reject ) => { this .then ((res ) => { resolve (res); }).catch (async (e) => { if (count > 0 ) { await Promise .prototype .sleep (delay); --count; console .log ('重试' , count); resolve (this .retry (count, delay)); } else { reject ('重试结束' ); } }); }); }; Promise .prototype .sleep = function (milliseconds ) { return new Promise ((resolve ) => setTimeout (resolve, milliseconds)); }; new Promise ((resolve, reject ) => reject ('test' )).retry (3 , 1000 );
提一下 yield* 参考文章 ,虽然与本文无关,但是记录一下。
yield *
表达式用于委托给另一个 generator 或可迭代对象。表达式迭代操作数,并产生它返回的每个值。我们可以看成使用此关键字让方法一步步执行,他会返回一个对象包含 value
(返回值) 和 done
(是否完成)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 function * yieldFunc (a, b, c ) { yield * [4 , 5 , 6 ]; yield * arguments ; console .log ('打印参数后的第一步' ); yield 'hello world' ; console .log ('即将结束' ); yield '下一步结束' ; console .log ('结束' ); } let runFuncs = yieldFunc (1 , 2 , 3 );runFuncs.next (); runFuncs.next (); runFuncs.next (); runFuncs.next (); runFuncs.next (); runFuncs.next (); runFuncs.next (); runFuncs.next (); runFuncs.next (); function * test (a, b ) { const x = yield (a + b); const y = yield x == 2 ; let z = 'hello world' ; if (y) { console .log ('认证成功!' ); z = '已登录' ; } else { console .log ('认证失败!' ); z = '未登录' ; } return z; } let authTest = test (1 , 1 );let hasNext = authTest.next ();console .log (hasNext);while (!hasNext.done ) { hasNext = authTest.next (hasNext.value ) console .log (hasNext); } let authTestTrue = test (1 , 1 );let next = authTestTrue.next ();console .log (next); next = authTestTrue.next (100 ); console .log (next); next = authTestTrue.next (true ); console .log (next);
经验法则 使用异步或阻塞代码时,请使用 Promise。 为了代码的可读性,resolve
方法对应 then, reject
对应 catch。 确保同时写入 .catch
和 .then
方法来实现所有的 Promise。 如果在 resolve/reject 两种情况下都需要做一些事情,请使用 .finally
。 我们每次改变单个 Promise (单一原则)。 我们可以在一个 Promise 中添加多个处理程序。 Promise 对象中所有方法的返回类型,无论是静态方法还是原型方法,都是 Promise。 在 Promise.all
中,无论哪个 Promise 首先未完成,Promise 的顺序都保持在值变量中。 基础部分参考公众号:前端小智