在JavaScript的世界中,所有代码都是单线程执行的。

由于这个“缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现:

  1. function callback() {
  2. console.log('Done');
  3. }
  4. console.log('before setTimeout()');
  5. setTimeout(callback, 1000); // 1秒钟后调用callback函数
  6. console.log('after setTimeout()');

观察上述代码执行,在Chrome的控制台输出可以看到:

  1. before setTimeout()
  2. after setTimeout()
  3. (等待1秒后)
  4. Done

可见,异步操作会在将来的某个时间点触发一个函数调用。

AJAX就是典型的异步操作。以上一节的代码为例:

  1. request.onreadystatechange = function () {
  2. if (request.readyState === 4) {
  3. if (request.status === 200) {
  4. return success(request.responseText);
  5. } else {
  6. return fail(request.status);
  7. }
  8. }
  9. }

把回调函数success(request.responseText)和fail(request.status)写到一个AJAX操作里很正常,但是不好看,而且不利于代码复用。

有没有更好的写法?比如写成这样:

  1. var ajax = ajaxGet('http://...');
  2. ajax.ifSuccess(success)
  3. .ifFail(fail);

这种链式写法的好处在于,先统一执行AJAX逻辑,不关心如何处理结果,然后,根据结果是成功还是失败,在将来的某个时候调用success函数或fail函数。

古人云:“君子一诺千金”,这种“承诺将来会执行”的对象在JavaScript中称为Promise对象。

Promise有各种开源实现,在ES6中被统一规范,由浏览器直接支持。

我们先看一个最简单的Promise例子:生成一个0-2之间的随机数,如果小于1,则等待一段时间后返回成功,否则返回失败:

  1. function test(resolve, reject) {
  2. var timeOut = Math.random() * 2;
  3. log('set timeout to: ' + timeOut + ' seconds.');
  4. setTimeout(function () {
  5. if (timeOut < 1) {
  6. log('call resolve()...');
  7. resolve('200 OK');
  8. }
  9. else {
  10. log('call reject()...');
  11. reject('timeout in ' + timeOut + ' seconds.');
  12. }
  13. }, timeOut * 1000);
  14. }

这个test()函数有两个参数,这两个参数都是函数,如果执行成功,我们将调用resolve('200 OK'),如果执行失败,我们将调用reject('timeout in ' + timeOut + ' seconds.')。可以看出,test()函数只关心自身的逻辑,并不关心具体的resolve和reject将如何处理结果。

有了执行函数,我们就可以用一个Promise对象来执行它,并在将来某个时刻获得成功或失败的结果:

  1. var p1 = new Promise(test);
  2. var p2 = p1.then(function (result) {
  3. console.log('成功:' + result);
  4. });
  5. var p3 = p2.catch(function (reason) {
  6. console.log('失败:' + reason);
  7. });

变量p1是一个Promise对象,它负责执行test函数。由于test函数在内部是异步执行的,当test函数执行成功时,我们告诉Promise对象:

  1. // 如果成功,执行这个函数:
  2. p1.then(function (result) {
  3. console.log('成功:' + result);
  4. });

当test函数执行失败时,我们告诉Promise对象:

  1. p2.catch(function (reason) {
  2. console.log('失败:' + reason);
  3. });

Promise对象可以串联起来,所以上述代码可以简化为:

  1. new Promise(test).then(function (result) {
  2. console.log('成功:' + result);
  3. }).catch(function (reason) {
  4. console.log('失败:' + reason);
  5. });

实际测试一下,看看Promise是如何异步执行的:

  1. // 清除log:
  2. var logging = document.getElementById('test-promise-log');
  3. while (logging.children.length > 1) {
  4. logging.removeChild(logging.children[logging.children.length - 1]);
  5. }
  6.  
  7. // 输出log到页面:
  8. function log(s) {
  9. var p = document.createElement('p');
  10. p.innerHTML = s;
  11. logging.appendChild(p);
  12. }
  13.  
  14. new Promise(function (resolve, reject) {
  15. log('start new Promise...');
  16. var timeOut = Math.random() * 2;
  17. log('set timeout to: ' + timeOut + ' seconds.');
  18. setTimeout(function () {
  19. if (timeOut < 1) {
  20. log('call resolve()...');
  21. resolve('200 OK');
  22. }
  23. else {
  24. log('call reject()...');
  25. reject('timeout in ' + timeOut + ' seconds.');
  26. }
  27. }, timeOut * 1000);
  28. }).then(function (r) {
  29. log('Done: ' + r);
  30. }).catch(function (reason) {
  31. log('Failed: ' + reason);
  32. });

可见Promise最大的好处是在异步执行的流程中,把执行代码和处理结果的代码清晰地分离了:

Promise还可以做更多的事情,比如,有若干个异步任务,需要先做任务1,如果成功后再做任务2,任何任务失败则不再继续并执行错误处理函数。

要串行执行这样的异步任务,不用Promise需要写一层一层的嵌套代码。有了Promise,我们只需要简单地写:

  1. job1.then(job2).then(job3).catch(handleError);

其中,job1、job2和job3都是Promise对象。

下面的例子演示了如何串行执行一系列需要异步计算获得结果的任务:

  1. 'use strict';
  2.  
  3. var logging = document.getElementById('test-promise2-log');
  4. while (logging.children.length > 1) {
  5. logging.removeChild(logging.children[logging.children.length - 1]);
  6. }
  7.  
  8. function log(s) {
  9. var p = document.createElement('p');
  10. p.innerHTML = s;
  11. logging.appendChild(p);
  12. }
  13.  
  14. // 0.5秒后返回input*input的计算结果:
  15. function multiply(input) {
  16. return new Promise(function (resolve, reject) {
  17. log('calculating ' + input + ' x ' + input + '...');
  18. setTimeout(resolve, 500, input * input);
  19. });
  20. }
  21.  
  22. // 0.5秒后返回input+input的计算结果:
  23. function add(input) {
  24. return new Promise(function (resolve, reject) {
  25. log('calculating ' + input + ' + ' + input + '...');
  26. setTimeout(resolve, 500, input + input);
  27. });
  28. }
  29.  
  30. var p = new Promise(function (resolve, reject) {
  31. log('start new Promise...');
  32. resolve(123);
  33. });
  34.  
  35. p.then(multiply)
  36. .then(add)
  37. .then(multiply)
  38. .then(add)
  39. .then(function (result) {
  40. log('Got value: ' + result);
  41. });

setTimeout可以看成一个模拟网络等异步执行的函数。现在,我们把上一节的AJAX异步执行函数转换为Promise对象,看看用Promise如何简化异步处理:

  1. 'use strict';
  2.  
  3. // ajax函数将返回Promise对象:
  4. function ajax(method, url, data) {
  5. var request = new XMLHttpRequest();
  6. return new Promise(function (resolve, reject) {
  7. request.onreadystatechange = function () {
  8. if (request.readyState === 4) {
  9. if (request.status === 200) {
  10. resolve(request.responseText);
  11. } else {
  12. reject(request.status);
  13. }
  14. }
  15. };
  16. request.open(method, url);
  17. request.send(data);
  18. });
  19. }
  20.  
  21. var log = document.getElementById('test-promise-ajax-result');
  22. var p = ajax('GET', '/api/categories');
  23. p.then(function (text) { // 如果AJAX成功,获得响应内容
  24. log.innerText = text;
  25. }).catch(function (status) { // 如果AJAX失败,获得响应代码
  26. log.innerText = 'ERROR: ' + status;
  27. });

除了串行执行若干异步任务外,Promise还可以并行执行异步任务。

试想一个页面聊天系统,我们需要从两个不同的URL分别获得用户的个人信息和好友列表,这两个任务是可以并行执行的,用Promise.all()实现如下:

  1. var p1 = new Promise(function (resolve, reject) {
  2. setTimeout(resolve, 500, 'P1');
  3. });
  4. var p2 = new Promise(function (resolve, reject) {
  5. setTimeout(resolve, 600, 'P2');
  6. });
  7. // 同时执行p1和p2,并在它们都完成后执行then:
  8. Promise.all([p1, p2]).then(function (results) {
  9. console.log(results); // 获得一个Array: ['P1', 'P2']
  10. });

有些时候,多个异步任务是为了容错。比如,同时向两个URL读取用户的个人信息,只需要获得先返回的结果即可。这种情况下,用Promise.race()实现:

  1. var p1 = new Promise(function (resolve, reject) {
  2. setTimeout(resolve, 500, 'P1');
  3. });
  4. var p2 = new Promise(function (resolve, reject) {
  5. setTimeout(resolve, 600, 'P2');
  6. });
  7. Promise.race([p1, p2]).then(function (result) {
  8. console.log(result); // 'P1'
  9. });

由于p1执行较快,Promise的then()将获得结果'P1'。p2仍在继续执行,但执行结果将被丢弃。

如果我们组合使用Promise,就可以把很多异步任务以并行和串行的方式组合起来执行。

来自廖雪峰老师的博客

所有的代码最好能够自己跟着敲一遍,进步来自每天一点点的积累!

Promise 初步的更多相关文章

  1. Promise (1) 初步接触

    总想着王者荣耀排位赛再提升个等级就弃掉游戏好好学习,然而打了两个周也没升上去,看来是应该换个方向发挥了. 最近看了<javascript Promise迷离书>,对Promise的理解颇有 ...

  2. 初步认识Promise

    在解释什么是Promise之前,先看一道练习题,做完练习题也就知道Promise到底是干嘛用的了. 假设现在有个需求:你要封装一个方法,我给你一个要读取文件的路径,你这个方法能帮我读取文件,并把内容返 ...

  3. 细说Promise

    一.前言 JavaScript是单线程的,固,一次只能执行一个任务,当有一个任务耗时很长时,后面的任务就必须等待.那么,有什么办法,可以解决这类问题呢?(抛开WebWorker不谈),那就是让代码异步 ...

  4. 手把手教你实现一个完整的 Promise

    用过 Promise,但是总是有点似懂非懂的感觉,也看过很多文章,还是搞不懂 Promise的 实现原理,后面自己边看文章,边调试代码,终于慢慢的有感觉了,下面就按自己的理解来实现一个 Promise ...

  5. 延期(deferred)的承诺(promise) — jq异步编程浅析

    引子 相信各位developers对js中的异步概念不会陌生,异步操作后的逻辑由回调函数来执行,回调函数(callback function)顾名思义就是“回头调用的函数”,函数体事先已定义好,在未来 ...

  6. 一步一步实现基于Task的Promise库(二)all和any方法的设计和实现

    在上一篇中我们已经初步完成了Task类,如果仅仅是这些,那么没有多大意义,因为网上这类js库有很多,现在我们来些更复杂的使用场景. 如果我们现在有这样一个需求:我们要先读取aa.txt的内容,然后去后 ...

  7. Promise初体验

    想通过回调函数做一个动画效果:三个小球依次运动,第一个小球运动到指定位置后回调第二个小球运动,依次类推,效果如图所示: 到第三个小球到达指定位置再回调,让第二个小球往回移动,直到最后一个小球回到原位: ...

  8. 彻底理解Promise对象——用es5语法实现一个自己的Promise(上篇)

    本文同步自我的个人博客: http://mly-zju.github.io/ 众所周知javascript语言的一大特色就是异步,这既是它的优点,同时在某些情况下也带来了一些的问题.最大的问题之一,就 ...

  9. ES6(promise)_解决回调地狱初体验

    一.前言 通过这个例子对promise解决回调地狱问题有一个初步理解. 二.主要内容 1.回调地狱:如下图所示,一个回调函数里面嵌套一个回调函数,这样的代码可读性较低也比较恶心 2.下面用一个简单的例 ...

随机推荐

  1. python strip()函数的用法

    函数原型 声明:s为字符串,rm为要删除的字符序列 s.strip(rm)         删除s字符串中开头.结尾处,位于 rm删除序列的字符 s.lstrip(rm)        删除s字符串中 ...

  2. android学习-Activity和Service的生命周期

    详细请跳转原网页Activity和Service的生命周期(图) 不解释,不懂算我输 Activity的生命周期(图) Service的声明周期

  3. linux 查找删除找定文件

    find . -name "*.lastUpdated" -exec rm -rf {} \; 这个命令是find的基本用法,可以分两部分,find ~/ -name " ...

  4. [BJOI 2018]求和

    Description 题库链接 给你一棵 \(n\) 个结点的有根树, \(m\) 次询问这棵树上一段路径上所有节点深度的 \(k\) 次方和. \(1\leq n\leq 300000,1\leq ...

  5. VS2013 项目项目安装和部署

    版权声明:本文为博主原创文章,未经博主允许不得转载. 1.release 模式下生成项目 2.解决方案   右键  添加   新建项目 其他项目类型 安装和部署 3.操作前将待打包项目发布路径指向上述 ...

  6. rabbitmq-channel方法介绍

    先介绍rabbmitmq的几个方法: // 声明一个队列 -// queue 队列名称 // durable 为true时server重启队列不会消失 (是否持久化) // exclusive 队列是 ...

  7. ASP.NET Core2,通过反射批量注入程序集

    public void ConfigureServices(IServiceCollection services)        {            string strValue = Con ...

  8. Java基础教程(16)--注解

    一.注解基础知识 1.注解的格式   最简单的注解就像下面这样: @Entity   @符号指示编译器其后面的内容是注解.在下面的例子中,注解的名称为Override: @Override void ...

  9. 五:Jquery-demo

    一:多选框的全选与全不选 1.遍历:使用each(); $("#checkallbox").click(function(){ var isChecked = this.check ...

  10. SEDA架构程序实现

    一.SEDA SEDA全称是:stage event driver architecture,中文直译为“分阶段的事件驱动架构”,它旨在结合事件驱动和多线程模式两者的优点,从而做到易扩展,解耦合,高并 ...