luckyyyyy / blog

William Chan's Blog
https://williamchan.me/
175 stars 28 forks source link

promise 和 generator #19

Open luckyyyyy opened 7 years ago

luckyyyyy commented 7 years ago

Auout Promise

Promise 作为 ECMAScript 6 的异步规范,为我们解决了很多异步回调问题,Ajax 出现的时候,刮来了一阵异步之风,现在 Nodejs 火爆,又一阵异步狂风刮了过来。需求是越来越苛刻,用户对性能的要求也是越来越高,随之而来的是页面异步操作指数般增长,如果不能恰当的控制代码逻辑,我们就会陷入无穷的回调地狱中。

由于 JavaScript 的单线程性质,我们必须等待上一个事件执行完成才能处理下一步,下面是一个很经典的例子。

$(function() { 
  // 获取模板 
  $.get(url, function(tpl) { 
    // 获取数据 
    $.get(url2, function(data) { 
    // 构建 DOMString 
      makeHtml(tpl, data, function(str) { 
      // 插入到 DOM 中 
        $(obj).html(str); 
      }); 
    }); 
  }); 
});

上面是一个很经典的例子,为了首屏快速加载,我们会把模板放在服务器,当用户获取数据时,我们会拉取模板并且拉取数据,最终拼接起来插入DOM中,但这就可能陷入了无限的回调地狱中,代码看起来既不美观又容易扰乱我们的思维,下面我们就用Promise来解决上面的问题。

// 伪代码
new Promise(ready).then(getTpl).then(getData).then(makeHtml).resolve();

如果变成上面这样,是不是就清晰多了?不过的确使用Promise上面的代码可以成立,那么我们就来详细介绍一下。

Deferred & Promise

在 Promise 之前,其实我们早已迎来的 jQuery 的 Deferred,当然它与 Promise 并不兼容,具体可以查看这里

创建 Promise 对象

// 创建一个promise对象
const promise = new Promise((resolve, reject) => {
  // do a thing, possibly async, then…
  if (/* everything turned out fine */) {
    resolve("Stuff worked!");
  } else {
    reject(Error("It broke"));
  }
});

Promise 构造函数包含一个参数和一个带有 resolve(解析)和 reject(拒绝)两个参数的回调。在回调中执行一些操作(例如异步),如果一切都正常,则调用 resolve,否则调用 reject,与普通旧版 JavaScript 中的 throw 一样,通常拒绝时会给出 Error 对象,但这不是必须的。Error 对象的优点在于它们能够捕捉堆追踪,因而使得调试工具非常有用。

// 使用刚才创建的promise对象
promise.then(result => {
  console.log(result); // "Stuff worked!"
}, err => {
  console.log(err); // Error: "It broke"
});

promise.then(result => {
  console.log(result); // "Stuff worked!"
}).catch(err => {
  console.log(err); // Error: "It broke"
});

then() 包含两个参数:一个用于成功情形的回调和一个用于失败情形的回调。这两个都是可选的,因此您可以只添加一个用于成功情形或失败情形的回调。 catch() 没有任何特殊之处,它只是 then(undefined, func) 的锦上添花,但可读性更强。注意,以上两个代码示例行为并不相同,后者相当于:

promise.then(result => {
  console.log(result); // "Stuff worked!"
}).then((undefined, err) => {
  console.log(err); // Error: "It broke"
})

两者之间的差异虽然很微小,但非常有用。Promise 拒绝后,将跳至带有拒绝回调的下一个 then()(或具有相同功能的 catch())。如果是 then(func1, func2),则 func1 或 func2 中的一个将被调用,而不会二者均被调用。但如果是 then(func1).catch(func2),则在 func1 拒绝时两者均被调用,因为它们在该链中是单独的步骤。

JavaScript promise 最初是在 DOM 中出现并称为Futures,之后重命名为Promises,最后又移入 JavaScript。在 JavaScript 中使用比在 DOM 中更好,因为它们将在如 Node.js 等非浏览器 JS 环境中可用(而它们是否会在核心 API 中使用 Promise 则是另外一个问题)。

尽管它们是 JavaScript 的一项功能,但 DOM 也能使用。实际上,采用异步成功/失败方法的所有新 DOM API 均使用 promise。

兼容性问题

现在前端的兼容性问题在babel出现后已经不存在较大的问题了,对于这种API,也基本用一种叫polyfill(垫片)的技术,当然 Promise 也有其响应的polyfill

下面列出已经原生支持的浏览器,IE全版本全军覆没。

Chrome Opera Firefox Safari Microsoft Edge
32+ 19+ 29+ 8+ all

Promise API 参考

方法名 说明
Promise.resolve(promise); 返回 Promise(仅当 promise.constructor == Promise 时)
Promise.resolve(thenable); 从 thenable 中生成一个新 Promise。thenable 是具有 then() 方法的类似于 Promise 的对象。
Promise.resolve(obj); 在此情况下,生成一个 Promise 并在执行时返回 obj。
Promise.reject(obj); 生成一个 Promise 并在拒绝时返回 obj。为保持一致和调试之目的(例如堆叠追踪), obj 应为 instanceof Error。
Promise.all(array); 生成一个 Promise,该 Promise 在数组中各项执行时执行,在任意一项拒绝时拒绝。每个数组项均传递给 Promise.resolve,因此数组可能混合了类似于 promise 的对象和其他对象。执行值是一组有序的执行值。拒绝值是第一个拒绝值。
Promise.race(array); 生成一个 Promise,该 Promise 在任意项执行时执行,或在任意项拒绝时拒绝,以最先发生的为准。

构造函数

new Promise((resolve, reject) => {});

// resolve(thenable)
// Promise 依据 thenable 的结果而执行/拒绝。

// resolve(obj)
// Promise 执行并返回 obj

// reject(obj)
// Promise 拒绝并返回 obj。为保持一致和调试(例如堆叠追踪),obj 应为 instanceof Error。 在构造函数回调中引发的任何错误将隐式传递给 reject()。

实例方法

promise.then(onFulfilled, onRejected)   

// 当/如果“promise”解析,则调用 onFulfilled。当/如果“promise”拒绝,则调用 onRejected。 两者均可选,如果任意一个或两者都被忽略,则调用链中的下一个 onFulfilled/onRejected。 两个回调都只有一个参数:执行值或拒绝原因。 then() 将返回一个新 promise,它相当于从 onFulfilled/onRejected 中返回、 通过 Promise.resolve 传递的值。如果在回调中引发了错误,返回的 promise 将拒绝并返回该错误。

promise.catch(onRejected)   
// promise.then(undefined, onRejected)

generator

ES6 还为我们提供了 generator,它可让某些功能在某个位置退出(类似于return),但之后能以相同位置和状态恢复,例如:

function *addGenerator() {
  let i = 0;
  while (true) {
    i += yield i;
  }
}

注意函数名称前面的星号,这表示 generator。yield 关键字是我们的返回/恢复位置。我们可按下述方式使用:

const adder = addGenerator();
adder.next().value; // 0
adder.next(5).value; // 5
adder.next(5).value; // 10
adder.next(5).value; // 15
adder.next(50).value; // 65

更深入的不在这里讲解,generator其实存在诸多问题,所以在ES7中,我们有了更好的方式,asyncawait,等下一篇详解。