乐趣区

关于javascript:JavaScript-Promise

JavaScript Promise

简略介绍一下 Promise 以及他的应用、异样解决、同步解决等等 …

介绍

  咱们都晓得 JavaScript 是一种同步编程语言,上一行出错就会影响下一行的执行,然而咱们须要数据的时候总不能每次都等上一行执行实现,这时就能够应用回调函数让它像异步编程语言一样工作。
  像 NodeJS 就是采纳异步回调的形式来解决须要期待的事件,使得代码会持续往下执行不必在某个中央期待着。然而也有一个不好的中央,当咱们有很多回调的时候,比方这个回调执行完须要去执行下个回调,而后接着再执行下个回调,这样就会造成层层嵌套,代码不清晰,很容易进入“回调监狱”。。。
  所以 ES6 新出的 Promise 对象以及 ES7 的 async、await 都能够解决这个问题。
  Promise 是用来解决异步操作的,能够让咱们写异步调用的时候写起来更加优雅,更加好看便于浏览。Promise 为承诺的意思,意思是应用 Promise 之后他必定会给咱们回答,无论胜利或者失败都会给咱们一个回答,所以咱们就不必放心他跑了哈哈。
  Promise 有三种状态:pending(未决定),resolved(实现fulfilled),rejected(失败)。只有异步返回时才能够扭转其状态,因而咱们收到的 Promise 过程状态个别只有两种:pending->fulfilled 或者 pending->rejected

应用

简略应用

间接上代码

function promiseTest(boolType = true) {return new Promise(function (resolve, reject) {
    // do something 而后返回一个 Promise 对象
    if (boolType) {resolve('胜利');
    } else {reject('失败');
    }
  });
}
// Promise 的 then 承受两个参数
// 第一个是胜利的 resolved 的胜利回调
// 另一个是失败的 rejected 的失败回调【可选】。// 并且 then 也能够返回 Promise 对象,这样就能够实现链式调用。// 栗子如下
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('第一次调用失败后尝试第二次胜利了!'));
// catch 不仅能够捕捉失败和 return Promise,也能够捕捉异样。promiseTest(true)
  .then((value) => value1)
  .catch((e) => console.log(e));

/* --- 打印后果 --- */
胜利后的解决 A
失败后的解决 B
失败后的解决 C
第一次调用失败后尝试第二次胜利了!ReferenceError: value1 is not defined at ...
/* --- 打印后果 --- */

另外当咱们须要在办法中期待 Promise 返回时,须要给办法增加 async 润饰,并应用 await 期待。

async function asyncFunc() { // 只有增加了 async 关键字,该办法的返回值就是一个 Promise。let result = await new Promise((resolve, reject) => {setTimeout(() => resolve(123), 2000);
  });
  return result;
}
asyncFunc(); // Promise {<pending>}
asyncFunc().then((value) => console.log(value)); // 123
await asyncFunc(); // 123

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 的胜利和失败状况的回调函数。

// promiseTest.then(onFulfilled[, onRejected]);
promiseTest.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)。

Promise.prototype.catch

catch() 办法返回一个 Promise,并且解决回绝的状况。它的行为与调用 Promise.prototype.then(undefined,onRejected) 雷同。事实上调用 obj.catch(onRejected) 其实就是 obj.then(undefined, onRejected)

// promiseTest.catch(onRejected);
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()各写一次 的状况。

promiseTest.finally(() => {// do my things});

promiseTest.then((result) => {
    // do my things
    return result;
  },
  (error) => {
    // do my things
    throw error;
  }
);

Promise.allSettled

该 Promise.allSettled() 办法返回一个在所有给定的 Promise 都曾经 fulfilled 或 rejected 后的 Promise,并带有一个对象数组,每个对象示意对应的 Promise 后果。

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 的是第一个抛出的错误信息。

/// 当咱们须要同步执行多个 Promise 的时候,能够应用 Promise.all() 来 "并发申请",缩小等待时间。/// 举个简略的栗子:/// 假如我须要三次申请获取数据,而后渲染页面。那么咱们看一下应用 Promise.all 和不应用的区别。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 就会解决或回绝。

/// 这个其实就是赛道的意思,哪个 Promise 先实现,就返回哪个。/// 举个简略的栗子:/// 假如咱们须要从三台服务器上拿取数据,那么那台先返回咱们就用哪台的数据。function race() {
  return Promise.race([new Promise((resolve, reject) => {
      // 第一台服务器 1s
      setTimeout(function () {resolve('123');
      }, 1000);
    }),
    new Promise((resolve, reject) => {
      // 第一台服务器 2s
      setTimeout(function () {resolve('456');
      }, 2000);
    }),
    new Promise((resolve, reject) => {
      // 第一台服务器 3s
      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() 是相同的。

{% note warning %}
留神:Promise.any() 办法仍然是实验性的,尚未被所有的浏览器齐全反对。它以后处于 TC39 第四阶段草案。
{% endnote %}

  Promise.any() 与 Promise.race() 办法不同,Promise.race() 办法次要关注 Promise 是否已解决 ,而 不论其被解决 (胜利) 还是被回绝(失败)。所以应用 Promise.any 来获取多台服务器数据时会更正当。

优雅的进行异样解决

详解

  • 之前刷视频有看到一些小问题:

    • 应用多个 await 时,前一个出现异常,如何不影响后续执行?
    • 咱们每次应用 Promise 都须要解决异样吗?
    • 如何对立解决异样和捕捉异步异样呢?
/// 咱们先定义几个函数来测试
function test1() {return new Promise((resolve, reject) => {setTimeout(function () {console.log('test1');
      resolve('test1');
    }, 1000); // 失常 1s 执行结束并胜利
  });
}

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() 必定是无奈执行的。
await test2();
await test1();
  • 这时候咱们须要这样写,然而这样尽管能够解决这个问题,然而如果后面的 Promise 数量一多,那么可读性就大大降低了!
await test2().catch((e) => console.log(e));
await test1();
或
try {await test2();
} catch (e) {console.log(e);
}
await test1();
  • 再联合前面两个问题,我查看了一些材料,包含 Dima Grossman 的 to.js,所以咱们能够采纳终极计划,话不多说间接上代码。
/**
 * 首先我参考了 to.js,扩大 Promise 原型办法,用来间接帮忙执行且解决异样。* @param {Function} res
 * @param {Function} rej
 * @returns
 */
Promise.prototype.to = function (res, rej) {return this.then((data) => {res && res(data);
    // console.log(data);
    return data;
  }).catch((err) => {rej && rej(err); // 可去除此行,全局定义处理错误函数,用以解决第三个问题。console.log(err); // 如果没定义后面的 rej 回调处理函数,咱们能够帮忙解决,例如此处能够帮咱们解决 test2 的异样。});
};
/**
 * 全局捕捉异样
 * @param {object} message
 * @param {object} source
 * @param {object} lineno
 * @param {object} colno
 * @param {object} error
 * @returns
 */
window.onerror = function (message, source, lineno, colno, error) {console.log('捕捉到异样:', { message, source, lineno, colno, error});
  //do something 全局解决
  return true; // return true 不在控制台报错
};
/// 这个能够帮忙咱们捕捉 test4 setTimeout 中的异步异样。

此时咱们再如此执行,均不会报错。

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}`)); // 如果须要自定义解决也能够传入回调函数,咱们的扩大 to 原型办法跟 then 一样是反对两个参数的。test2().to();
test3().to();
test4().to(); 
console.log('后面报错仍然会执行');

多说几句

另外补充一下,说到 Promise 的优雅解决,咱们平时写的时候返回不要像上面一样嵌套应用。

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 这里捕捉到谬误

request1()
  .then((res1) => request2(res1))
  .then((res2) => request3(res2))
  .then((res3) => console.log(res3))
  .catch((e) => console.log('异样解决', e));
// 没错就是这样,作为强迫症程序员,就是要优雅(*v*)!

实现 Promise Retry

Promise.prototype.retry = function (count = 0, delay = 0) {return new Promise((resolve, reject) => {this.then((res) => {resolve(res);
    }).catch(async (e) => {if (count > 0) {
        // 此处也可应用 setTimeout 实现
        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(是否实现)。

  • 栗子
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(); // {value: 4, done: false}
runFuncs.next(); // {value: 5, done: false}
runFuncs.next(); // {value: 6, done: false}
runFuncs.next(); // {value: 1, done: false}
runFuncs.next(); // {value: 2, done: false}
runFuncs.next(); // {value: 3, done: false}
runFuncs.next(); // 打印参数后的第一步,{value: "hello world", done: false}
runFuncs.next(); // 行将完结,{value: "下一步完结", done: false}
runFuncs.next(); // 完结,{value: undefined, done: true}

// 如果咱们一个验证须要多步,咱们能够给 next() 传参,传递的值在原函数体中会变成上步失去的后果。function* test(a, b) {const x = yield (a + b);
  // x 的值是咱们依据第一步的后果判断后,通过 next 传递给他的。const y = yield x == 2; // 例如此处:xxx.next(6) 则 x = 6; xxx.next(7) 则 x = 7; 而不论咱们传递的 a b 是什么。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);
}
// {value: 2, done: false}
// {value: true, done: false}
// 认证胜利!// {value: '已登录', done: true}

let authTestTrue = test(1, 1);
let next = authTestTrue.next();
console.log(next); // {value: 2, done: false}
next = authTestTrue.next(100);
console.log(next); // {value: false, done: false}
next = authTestTrue.next(true);
// 认证胜利!console.log(next); // {value: '已登录', done: true}

教训法令

  • 应用异步或阻塞代码时,请应用 Promise。
  • 为了代码的可读性,resolve 办法对应 then, reject 对应 catch。
  • 确保同时写入 .catch.then 办法来实现所有的 Promise。
  • 如果在 resolve/reject 两种状况下都须要做一些事件,请应用 .finally
  • 咱们每次扭转单个 Promise (繁多准则)。
  • 咱们能够在一个 Promise 中增加多个处理程序。
  • Promise 对象中所有办法的返回类型,无论是静态方法还是原型办法,都是 Promise。
  • Promise.all 中,无论哪个 Promise 首先未实现,Promise 的程序都放弃在值变量中。

根底局部参考公众号:前端小智

退出移动版