Javascript异步详解(二)-Promise(1)

1,126 阅读7分钟

1.什么是Promise

1.1 Promise简介

    上篇文章中我们介绍了回调函数及其问题,最重要的两个问题就是控制反转和回调地狱的问题。在回调的模式中,我们将控制权交给了第三方,期待它在正确的时候调用我们回调函数实现正常功能。但是第三方很容易未调用、错误调用和重复调用回调函数造成问题。试想一下我们把控制权再反转过来,第三方提供一个我们能知道其任务何时结束的能力,并由我们自己处理接下来该做的事情,这种范式就是Promise。

1.2 控制反转的解决和链式调用

    Promise并没有摈弃回调,只是把回调的安排转交给了一个位于我们和其他工具之间的可信任 的中介机制,类似于事件管理。看下下面代码:

function foo(x) { 
   //费时操作
   return listner;
}var evt = foo( 42 );   return listner;
}var evt = foo( 42 );evt.on( "resolve", function(){ // 可以进行下一步了!} );
evt.on( "reject", function(err){ // foo(..)中出错了} )

    evt是一个Promise类似的实现,显式的返回了一个事件监听器,并注册了两个事件。Promise就是位于我们和第三方的一个可信任的中间机制。可以看到我们并没有按照回调函数的模式将回调函数传给foo,而是返回一个叫evt的事件监听对象,由这个事件监听对象来接受回调。由于回调本身是一种控制反转,所以Promise模式提供了控制反转反转,把控制权交给调用代码。同时,Promise还有两个特性:1.Promise的内部的状态只会收到异步操作结果影响,不会受到外部影响。2. Promise内部状态一旦改变就不会再次变化。

    Promise不是一个单步操作,而是可以把多个Promise链接到一起来表示一系列的异步操作。这种方式可以实现的关键在于以下两个 Promise 固有行为特性:

  • 每次你对 Promise 调用 then(..),它都会创建并返回一个新的 Promise,我们可以将其 链接起来;

  • 不管从 then(..) 调用的完成回调(第一个参数)返回的值是什么,它都会被自动设置 为被链接 Promise(第一点中的)的完成。 

2. Promise API

1.3.1 new Promise 构造器

    Promise() 必须和 new 一起使用,并且必须提供一个函数回调。这个 回调是同步的或立即调用的。这个函数接受两个函数回调,用以支持 promise 的决议。通 常我们把这两个函数称为 resolve() 和 reject() 。

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

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

1.3.2 Promise.resolve和Promise.reject()

Promise.resolve用于创建一个已经完成的Promise,而Promise.reject用于创建一个被拒绝的Promise

Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))

1.3.3 then(..) 和 catch(..)

    每个 Promise 实例(不是 Promise API 命名空间)都有 then(..) 和 catch(..) 方法,通过 这两个方法可以为这个 Promise 注册完成和拒绝处理函数。Promise 决议之后,立即会调用 这两个处理函数之一,但不会两个都调用,而且总是异步调用。

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

then(..) 和 catch(..) 也会创建并返回一个新的 promise,这个 promise 可以用于实现 Promise 链式流程控制。如果完成或拒绝回调中抛出异常,返回的 promise 是被拒绝的。 

1.3.4 Promise.all([ .. ]) 和 Promise.race([ .. ])

    对Promise.all([ .. ])来说,只有传入的所有promise都完成,返回promise才能完成。 如果有任何 promise 被拒绝,返回的主 promise 就立即会被拒绝(抛弃任何其他 promise 的结果)。如果完成的话,你会得到一个数组,其中包含传入的所有 promise 的完成值。对于 拒绝的情况,你只会得到第一个拒绝 promise 的拒绝理由值。这种模式传统上被称为门: 所有人都到齐了才开门。

对Promise.race([ .. ])来说,只有第一个决议的promise(完成或拒绝)取胜,并且其 决议结果成为返回 promise 的决议。这种模式传统上称为门闩:第一个到达者打开门闩通 过。 

// 生成一个Promise对象的数组
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
  return getJSON('/post/' + id + ".json");
});

Promise.all(promises).then(function (posts) {
  // ...
}).catch(function(reason){
  // ...
});
const p = Promise.race([
  fetch('/resource-that-may-take-a-while'),
  new Promise(function (resolve, reject) {
    setTimeout(() => reject(new Error('request timeout')), 5000)
  })
]);

p
.then(console.log)
.catch(console.error);

3. Promise的局限

3.1 顺序错误处理 

    如果构建了一个没有错误处理函数的 Promise 链,链中任何地方的任何错误都会在链中一 直传播下去,直到被查看(通过在某个步骤注册拒绝处理函数)。还有,这个 Promise 链中的任何一个步骤都没有显式地处理自身错误。这意味着你可以在 p 上注册一个拒绝错误处理函数,对于链中任何位置出现的任何错误,这个处理函数都会得到通知。

3.2 单决议

    Promise 最本质的一个特征是:Promise 只能被决议一次(完成或拒绝)。在许多异步情况中,你只会获取一个值一次,所以这可以工作良好。但是,还有很多异步的情况适合另一种模式——一种类似于事件和 / 或数据流的模式。如果不在 Promise 之上构建显著的抽象,Promise 肯定完全无法支持多值决议处理 ,比如一个按钮的多次触发等。

 3.3 无法取消和无法获取进度

   一旦创建了一个 Promise 并为其注册了完成和 / 或拒绝处理函数,如果出现某种情况使得这个任务悬而未决的话,你也没有办法从外部停止它的进程。单独的 Promise 不应该可取消,但是取消一个可序列是合理的,因为你不会像对待 Promise 那样把序列作为一个单独的不变值来传送。所以有的Promise库会提供abort的方式来停止一个链式调用,同时Promise无法获取进度。

4.Promise的性能

    把基本的基于回调的异步任务链与 Promise 链中需要移动的部分数量进行比较。很显然, Promise 进行的动作要多一些,这自然意味着它也会稍慢一些。请回想 Promise 提供的信任保障列表,再与你要在回调之上建立同样的保护自建的解决方案来比较一下。更多的工作,更多的保护。这些意味着 Promise 与不可信任的裸回调相比会更慢一些。 

    但是与之相对的你得到了大量的内建的可信任性和更易于理解异步流表达模式,解决了通过回调模式产生的控制反转问题和回调异步模式的难以理解。所以我的观点是Promise非常好,完全可以尽情使用。