【译】JavaScript Promise API

时间:2021-08-17 04:03:39

原文地址:JavaScript Promise API

在 JavaScript 中,同步的代码更容易书写和 debug,但是有时候出于性能考虑,我们会写一些异步的代码(代替同步代码)。思考这样一个场景,同时触发几个异步请求,当所有请求到位时我们需要触发一个回调,怎么做?Promise 让一切变的简单,越来越多的原生 API 基于 Promise 去实现。那么,什么是 Promise?Promise API 如何使用?

基于 Promise 的 原生 API

Promise 主要是为了解决异步的回调地狱。我们熟悉的 XMLHttpRequest API 可以异步使用,但是它没有基于 Promise API。一些原生的 API 已经使用了 Promise,比如:

  • Battery API
  • fetch API(下一代 XHR)
  • ServiceWorker API

对于 Promise 的测试其实非常简单,使用 SetTimeout 就能当做一个异步的事件来测试。

Promise 基本用法

Promise 本质其实是一个构造函数,其接受一个函数作为参数,而这个函数内部一般会写一些异步事件处理的代码,比如 SetTimeout 或者 XMLHttpRequest。异步事件我们一般都会有一个 "失败" 的处理机制,我们还可以给这个作为参数的函数传入两个参数 resolve 和 reject,分别表示异步事件 "成功" 和 "失败" 时的回调函数。

let p = new Promise((resolve, reject) => {
  // Do an async task
  setTimeout(() => {
    // good condition
    if (Math.random() > 0.5) {
      resolve('The number is bigger than 0.5');
    } else {
      reject('The number is smaller than 0.5');
    }
  }, 1000);
});

p.then(data => {
  // do something with the result
  console.log(data);
}, data => {
  // do something with the result
  console.log(data);
});

对于什么时候调用 resolve(可以粗略理解为异步操作成功)或者 reject(可以粗略理解为异步操作失败)作为异步事件的回调函数,完全取决于开发者。

以下的代码我们将 XMLHttpRequest 基于 Promise 去实现:

// From Jake Archibald's Promises and Back:
// http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promisifying-xmlhttprequest

function get(url) {
  // Return a new promise.
  return new Promise(function(resolve, reject) {
    // Do the usual XHR stuff
    var req = new XMLHttpRequest();
    req.open('GET', url);

    req.onload = function() {
      // This is called even on 404 etc
      // so check the status
      if (req.status == 200) {
        // Resolve the promise with the response text
        resolve(req.response);
      }
      else {
        // Otherwise reject with the status text
        // which will hopefully be a meaningful error
        reject(Error(req.statusText));
      }
    };

    // Handle network errors
    req.onerror = function() {
      reject(Error("Network Error"));
    };

    // Make the request
    req.send();
  });
}

// Use it!
get('story.json').then(function(response) {
  console.log("Success!", response);
}, function(error) {
  console.error("Failed!", error);
});

Promise 构造函数调用 new 操作符,传入一些异步事件的代码,会生成一个 Promise 实例对象,但是有的时候,我们需要生成一个 Promise 的实例对象,但是并不需要执行一些异步代码,我们可以用 Promise.resolve() 和 Promise().reject() 来做这件事情。

var userCache = {};

function getUserDetail(username) {
  // In both cases, cached or not, a promise will be returned

  if (userCache[username]) {
    // Return a promise without the "new" keyword
    return Promise.resolve(userCache[username]);
  }

  // Use the fetch API to get the information
  // fetch returns a promise
  return fetch('users/' + username + '.json')
    .then(function(result) {
      userCache[username] = result;
      return result;
    })
    .catch(function() {
      throw new Error('Could not find user: ' + username);
    });
}

getUserDetail() 会始终返回一个 Promise 实例,所以 then 等方法可以在该函数返回值(即 Promise 实例)中使用。

then

所有 Promise 实例均拥有 then 方法,then 方法上可以定义两个回调函数,第一个回调函数能接收到实例化 Promise 时通过 resolve 方法传递过来的参数(必须),而第二个回调函数对应 reject 方法(可选)。

new Promise(function(resolve, reject) {
  // A mock async action using setTimeout
  setTimeout(function() { resolve(10); }, 1000);
})
.then(function(result) {
  console.log(result);
});

// From the console:
// 10

当这个 Promise 实例内部调用 resolve 方法时(Pending -> Resolved),then 中的第一个参数代表的回调函数被触发。

then 也能被链式调用。

new Promise(function(resolve, reject) {
  // A mock async action using setTimeout
  setTimeout(function() { resolve(10); }, 3000);
})
.then(function(num) { console.log('first then: ', num); return num * 2; })
.then(function(num) { console.log('second then: ', num); return num * 2; })
.then(function(num) { console.log('last then: ', num);});

// From the console:
// first then:  10
// second then:  20
// last then:  40

在 then 方法中,可以直接 return 数据而不是 Promise 对象,在后面的 then 中就可以接受到数据了。

catch

catch 方法有两个作用。

第一个作用可以代替 then 方法的第二个参数。

let p = new Promise((resolve, reject) => {
  // Do an async task
  setTimeout(() => {
    // good condition
    if (Math.random() > 0.5) {
      resolve('The number is bigger than 0.5');
    } else {
      reject('The number is smaller than 0.5');
    }
  }, 1000);
});

p.then(data => {
  // do something with the result
  console.log(data);
}).catch(data => {
  // replace the second argument of then function
  console.log(data);
});

第二个作用有点像 try-catch,尝试捕获错误。在执行 resolve 的回调(也就是 then 中的第一个参数)时,如果抛出异常了(代码出错),那么会进入这个 catch 方法中。

let p = new Promise((resolve, reject) => {
  // Do an async task
  setTimeout(() => {
    // good condition
    if (Math.random() > 0) {
      resolve('The number is bigger than 0');
    }
  }, 1000);
});

p.then(data => {
  // do something with the result
  console.log(data);
  throw new Error();
}).catch(error => {
  console.log('There is an error.');
});

// The number is bigger than 0
// There is an error.

Promise.all

回到本文开头说的应用场景,如果多个异步事件同时请求,我们需要在所有事件完成后触发回调,Promise.all 方法可以满足。该方法接收一个 Promise 实例组成的数组作为参数,当该数组中的所有 Promise 实例的状态变为 resolved 时,触发回调方法,回调方法的参数是由所有 Promise 实例的 resolve 函数的参数组成的数组。

Promise.all([promise1, promise2]).then(function(results) {
  // Both promises resolved
})
.catch(function(error) {
  // One or more promises was rejected
});

Promise.all 可以和 fetch 结合使用,因为 fetch 方法始终返回 Promise 实例。

var request1 = fetch('/users.json');
var request2 = fetch('/articles.json');

Promise.all([request1, request2]).then(function(results) {
  // Both promises done!
});

有任意的 Promise 实例抛出异常就会进入 catch 方法,但是需要注意的是,异步事件并不会停止执行

var req1 = new Promise(function(resolve, reject) {
  // A mock async action using setTimeout
  setTimeout(function() {
    resolve('First!');
    console.log('req1 ends!');
  }, 4000);
});
var req2 = new Promise(function(resolve, reject) {
  // A mock async action using setTimeout
  setTimeout(function() { reject('Second!'); }, 3000);
});
Promise.all([req1, req2]).then(function(results) {
  console.log('Then: ', results);
}).catch(function(err) {
  console.log('Catch: ', err);
});

// From the console:
// Catch: Second!
// req1 ends!

Promise.race

Promise.race 接收参数和 Promise.all 类似,但是有任何实例状态变为 resolved 或者 rejected 时,就会调用 then 或者 catch 中的回调。很显然,它的回调的参数是一个值,并不是一个数组。

var req1 = new Promise(function(resolve, reject) {
  // A mock async action using setTimeout
  setTimeout(function() { resolve('First!'); }, 8000);
});
var req2 = new Promise(function(resolve, reject) {
  // A mock async action using setTimeout
  setTimeout(function() { reject('Second!'); }, 3000);
});
Promise.race([req1, req2]).then(function(one) {
  console.log('Then: ', one);
}).catch(function(one, two) {
  console.log('Catch: ', one);
});

// From the console:
// Then: Second!

对于 Promise.race 方法,需要注意的是,虽然有一个异步事件有了结果,便会执行 then 或者 catch 中的回调,但是其他的异步事件其实还会继续执行。

var req1 = new Promise(function(resolve, reject) {
  // A mock async action using setTimeout
  setTimeout(function() {
    resolve('First!');
    console.log('req1 end!');
  }, 8000);
});
var req2 = new Promise(function(resolve, reject) {
  // A mock async action using setTimeout
  setTimeout(function() { reject('Second!'); }, 3000);
});
Promise.race([req1, req2]).then(function(one) {
  console.log('Then: ', one);
}).catch(function(one, two) {
  console.log('Catch: ', one);
});

// From the console:
// Then: Second!
// req1 end!

有个简单的应用,有个文件的请求我们有多个地址,很显然请求到了任意一个即可。

熟悉 Promise

我们有必要掌握 Promise,Promise 可以有效防止回调地狱,优化异步交互,使得异步代码更加直观。而且越来越多的原生 API 基于 Promise 去实现,我们有必要知其然,知其所以然。

【译】JavaScript Promise API的更多相关文章

  1. JavaScript Promise API

    同步编程通常来说易于调试和维护,然而,异步编程通常能获得更好的性能和更大的灵活性.异步的最大特点是无需等待."Promises"渐渐成为JavaScript里最重要的一部分,大量的 ...

  2. Javascript Promise入门

    是什么? https://www.promisejs.org/ What is a promise? The core idea behind promises is that a promise r ...

  3. [Javascript] Promise

    Promise 代表着一个异步操作,这个异步操作现在尚未完成,但在将来某刻会被完成. Promise 有三种状态 pending : 初始的状态,尚未知道结果 fulfilled : 代表操作成功 r ...

  4. Javascript Promise 学习笔记

    1.     定义:Promise是抽象异步处理对象以及对其进行各种操作的组件,它把异步处理对象和异步处理规则采用统一的接口进行规范化. 2.     ES6 Promises 标准中定义的API: ...

  5. JavaScript Promise:去而复返

    原文:http://www.html5rocks.com/en/tutorials/es6/promises/ 作者:Jake Archibald 翻译:Amio 女士们先生们,请准备好迎接 Web ...

  6. Promise API

    Promise API     刚刚接触promise这个东西,网上看了很多博客,大部分是讲怎么用Promise,丝毫没提怎么实现Promise. 我不甘 心,可是真去看JQuery或者Angular ...

  7. Promise API 简介

    Promise API 简介 译者注: 到处是回调函数,代码非常臃肿难看, Promise 主要用来解决这种编程方式, 将某些代码封装于内部. Promise 直译为"承诺",但一 ...

  8. javascript的api设计原则

    前言 本篇博文来自一次公司内部的前端分享,从多个方面讨论了在设计接口时遵循的原则,总共包含了七个大块.系卤煮自己总结的一些经验和教训.本篇博文同时也参考了其他一些文章,相关地址会在后面贴出来.很难做到 ...

  9. 深入理解javascript选择器API系列第三篇——h5新增的3种selector方法

    × 目录 [1]方法 [2]非实时 [3]缺陷 前面的话 尽管DOM作为API已经非常完善了,但是为了实现更多的功能,DOM仍然进行了扩展,其中一个重要的扩展就是对选择器API的扩展.人们对jQuer ...

随机推荐

  1. jquery练习(一次性赋予多个属性值)

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  2. Mongodb Manual阅读笔记:CH5 安全性

    5 安全性 Mongodb Manual阅读笔记:CH2 Mongodb CRUD 操作Mongodb Manual阅读笔记:CH3 数据模型(Data Models)Mongodb Manual阅读 ...

  3. PHP curl 采集内容之规则 1

    <?phpheader("Content-type:text/html; charset=utf-8");$pattern = '/xxx(.*)yyyy/isU'; //i ...

  4. OCP读书笔记&lpar;6&rpar; - 手动恢复操作

    6.Restore and Recovery Task 非关键性文件丢失的恢复 临时文件丢失的恢复 临时表空间文件丢失的恢复: 查看数据库中的临时文件: SQL> select file#,ST ...

  5. JDK--box和unbox

    目录 什么是装箱.拆箱 基本类型和包装类型 为什么会有基本类型? 为什么还要有包装类型 两者区别 两者互转 源码分析(JDK1.8版本) valueOf方法 1.Integer.Short.Byte. ...

  6. 测试面试话题8&colon;测试人员如何让开发少写bug?

    在测试过程中和不同开发合作,往往会发现一些bug都是大多数开发人员常出现的错误,为了帮助开发人员,也减少测试的重复工作量,非常有必要将以往出现的bug做整理,分析原因,让开发知道这些bug, 避免再次 ...

  7. Java开发学习--Java 中基本类型和包装类之间的转换

    Java 中基本类型和包装类之间的转换 基本类型和包装类之间经常需要互相转换,以 Integer 为例(其他几个包装类的操作雷同哦): 在 JDK1.5 引入自动装箱和拆箱的机制后,包装类和基本类型之 ...

  8. 运用busybox构建最小根文件系统

    平台:vmware下ubuntu14.04前期准备:安装交叉编译环境arm-linux-gcc-4.5.1;下载完成BusyBox 1.23.2一.busybox构建1.make menuconfig ...

  9. 【linux】ulimit限制打开的文件数量

    以限制打开文件数为例. ulimit -Hn 查看硬限制. ulimit -Sn 查看软限制. ulimit -n 查看两个中更小的限制(软限制始终比硬限制低, 所以查看的是软限制) 设定规则 1.软 ...

  10. Hibernate学习第4天--HQL——QBC查询详解,抓取策略优化。

    上次课回顾: l  Hibernate的一对多 n  表与表之间关系 u  一对多关系 u  多对多关系 u  一对一关系 n  Hibernate的一对多配置 u  搭建Hibernate基本环境 ...