# 十四、承诺

承诺提供了一个标准的实现，可以在不使用回调的情况下处理 JavaScript 中的异步编程。使用回调模式的最大副作用之一是你的代码只用几级回调就会变得非常丑陋。

承诺允许您比使用回调更容易地开发异步脚本。承诺代表一种我们在未来某个时候可以处理的价值。与回调相比，承诺给了我们两个主要优势:

*   该值的任何其他注册处理程序都不能更改它。承诺合同是不可改变的。
*   我们保证会收到这个值，不管我们什么时候为它注册一个处理程序，即使它已经被解析了。这与事件形成对比，因为一旦事件被触发，您就不能在以后访问它的值。

让我们看看下面的例子:

代码清单 195

```js
  var p2 =
  Promise.resolve("foo"); 
  p2.then((res)
  => console.log(res)); 

  var p = new Promise(function(resolve,
  reject) {  

  setTimeout(() => resolve(4), 2000);
  });

  p.then((res)
  => {  

  res += 2; 

  console.log(res);
  });

  p.then((res)
  => console.log(res));

```

在第一行，我们创造一个承诺，并立即解决它。这说明了第二个优势。与事件不同，我们仍然可以兑现承诺并从中获得价值。接下来，我们定义一个标准承诺，并让它在两秒钟后解决。我们的处理者从承诺中获得价值，但不能改变承诺本身的价值。这是好的，因为我们的承诺是不可变的，并且在多次调用时总是返回相同的结果。

以下是前一个示例的输出:

代码清单 196

```js
  foo
  6
  4

```

到目前为止，我们只解决了承诺。当我们拒绝一个承诺时怎么办？考虑以下代码:

代码清单 197

```js
  var p = new Promise(function(resolve,
  reject) {  

  setTimeout(() => reject("Timed out!"), 2000);
  });

  p.then((res)
  => console.log(res),
       (err)
  => console.log(err));

```

这一次，我们在两秒钟的延迟后呼叫`reject`。我们看到`then()`也可以接收第二个错误处理程序。

以下是前一个示例的输出:

代码清单 198

```js
  Timed out!

```

但是等等，你可以用更不同的方式写这个！考虑以下代码:

代码清单 199

```js
  var p = new Promise(function(resolve,
  reject) {  

  setTimeout(() => reject("Timed out!"), 2000);
  });

  p.then((res)
  => console.log("Response:",
  res))

  .catch((err) => console.log("Error:",
  err));

```

你也可以使用诺言的捕捉功能。我们的输出将完全相同:

代码清单 200

```js
  Error: Timed out!

```

让我们考虑另一个例子，当异常发生时。考虑以下代码:

代码清单 201

```js
  var p = new Promise(function(resolve,
  reject) {  

  setTimeout(() => {throw new
  Error("Error
  encountered!");}, 2000);
  });

  p.then((res)
  => console.log("Response:",
  res))

  .catch((err) => console.log("Error:",
  err));

```

扔个`Error`跟叫`reject()`一样。您能够捕捉到错误并进行相应的处理。

以下是前一个示例的输出:

代码清单 202

```js
  Error encountered!

```

承诺的一个好处是许多同步工具仍然有效，因为基于承诺的函数返回结果。

考虑以下示例:

代码清单 203

```js
  var fileUrls = [

  'http://example.com/file1.txt',

  'http://example.com/file2.txt'
  ];
  var httpGet = function(item) {

  return new
  Promise(function(resolve, reject)
  {  

  setTimeout(() => resolve(item), 2000);

  });
  };
  var promisedTexts =
  fileUrls.map(httpGet);

  Promise.all(promisedTexts)

  .then(function (texts) {

  texts.forEach(function (text) {

  console.log(text);

  });

  })

  .catch(function
  (reason) {
      // Receives first rejection among the promises

  });

```

在这个例子中，我们使用异步调用来加载文件。这段代码的优雅之处在于`then()`在所有承诺完成之前不会被调用。但是，如果任何承诺失败，将调用`catch()`处理程序。

有时候我们不想等到所有的承诺都完成了；相反，我们希望获得数组中第一个要实现的承诺的结果。我们可以通过`Promise.race()`做到这一点，就像`Promise.all()`一样，它需要一系列的承诺。然而，与`Promise.all()`不同的是，只要该数组中的第一个承诺实现，它就会实现返回的承诺。

代码清单 204

```js
  function delay(ms) {  

  return new
  Promise((resolve, reject) => {

  setTimeout(resolve, ms);

  });
  }

  Promise.race([ 

  delay(3000).then(() => "I
  finished second."),

  delay(2000).then(() => "I
  finished first.")
  ])
  .then(function(txt) {

  console.log(txt);  
  })
  .catch(function(err)
  {

  console.log("error:", err);
  });

```

这里，我们使用了一个助手函数`delay()`，它将在一定时间后超时。我们以数组的形式传递我们的承诺。接下来，我们简单地把发生的事情说出来。

以下是前一个示例的输出:

代码清单 205

```js
  I finished first.

```

如你所见，第一个兑现的承诺是我们在`then()`处理器中得到的。