JavaScript学习笔记(5) 异步-- Promise

写在前面

异步编程对Javascript语言非常重要,在Javascript的发展道路上,异步编程的方法也是一直在不断更新。关于这方面的知识,网上已经有很多成熟的教程和讲解,我将对这些教程进行整理和归纳,整理出异步JS异步编程的几种解决方法。

Javascript的异步执行

Javascript的执行环境是单线程的,所谓的单线程就是一次只能完成一个任务,其任务的调度方式就是排队,这就和超市等待付款一样,前面的人还没有结账,后面的人就只能等着。这种模式的坏处很明显,就是如果有一个任务耗时很长,就会拖延整个程序的执行。为了解决这个问题,Javascript将任务的执行模式分为两种:同步(synchronous)和异步(Asynchronous)。

  • 同步模式就是前面提到的这种,后一个任务等待前一个任务结束再执行,程序执行的顺序与任务排列的顺序是一致的,同步的。
  • 异步模式,通俗点说,就是前面排队的人告诉后面排队的一个准确时间,这样后面的人就可以利用这段时间去干些别的事情。异步模式可以通过回调函数实现,假设我们需要进行一个耗时的数据请求,在对外部数据发送请求后,程序的执行权将会交给别的任务,一直等到外部数据返回以后,系统才会通知执行回调函数,而回调函数中通常会包括对获取的数据的处理方法。所以,在这个模式下,程序的执行顺序和任务的排列顺序是不一致的,异步的。

Promise

Promise是异步编程的一种解决方案,ES6原生提供了Promise对象。

特点

  • Promise对象有三种状态:Pending(进行中),Resolved(已完成),Rejected(已失败),只有异步操作的结果可以决定当前是哪种状态。
  • Promise的状态改变可能有两种情况:pending->Resolved或者pending->Rejected,一旦发生,状态就会保持不变。

基本用法

  • Promise对象是一个构造函数,用来生成Promise实例。
  • Promise构造函数接受一个函数作为参数,该函数的两个参数分别为resolve和reject,他们是两个函数,由JS引擎提供,不用自己部署。
  • resolve函数在异步操作成功时调用,并将异步操作的结果,作为参数传递出去。
  • reject函数在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
  • then方法可以接受两个回调函数作为参数,第一个回调函数时Promise对象的状态变为resolved时候调用,第二个是Promise对象状态变为reject时调用,第二个函数可选。
  • 如果Promise对象状态变为reject时,会调用catch方法指定的回调函数处理这个错误,所以,除了定义then的第二个参数以外,也可以定义catch方法的回调函数来处理Promise抛出的错误。
例1 (简单例子)
function test(resolve,reject){
    var timeout=Math.random()*2;
    console.log('设置timeout为'+timeout+'秒');
    setTimeout(function () {
      if(timeout<1){
        console.log('call resolve()');
        resolve(" resolved");
      }else {
        console.log('call reject()');
        reject(" rejected");
      }
    }, timeout*1000);
}

//写法1
var p1=new Promise(test);
p1.then(function(result){
  console.log("成功"+result);
},function(result){
  console.log("失败"+result);
});

//写法2
var p1=new Promise(test);
var p2=p1.then(function(result){
  console.log("成功"+result);
});

var p3=p2.catch(function(result){
  console.log("失败"+result);
})

/*
输出:
   设置timeout为1.3983493377635763秒
   call reject()
   失败 rejected
或者:
   设置timeout为0.8257771710631485秒
   call resolve()
   成功 resolved
  */

这个例子中p1是promise实例,方法一和方法二所表达的意思是一样的,只是写法不同,方法1用的就是上面说过的给then传递两个回调函数作为参数的写法。方法二只将then用于函数执行成功的情况,而使用catch来处理函数执行不成功的情况。一般倾向于使用catch方法定义reject状态的回调函数。
上述的test函数执行成功的情况下,即随机数小于1的情况下,我们将调用resolve(' resolved'),在执行失败的情况下,将调用reject(" rejected")。变量p1是一个Promise对象,负责执行test函数。当test函数执行成功时,then方法指定的回调函数,将在当前脚本所有同步任务执行完后执行,也就是过了随机数产生的秒数以后执行这里的console.log("成功"+result)。相同的,当test函数执行失败时,catch方法中指定的回调函数也会在test中的所有任务执行完后执行。
上面提到的console.log("成功"+result)中的result其实就是等于" resolved",因为如果resolve函数和reject函数带有参数,那么它们的参数就会被传递给回调函数。通常情况下,reject函数的参数会使Error对象的实例,表示抛出的错误;resolve函数的参数可能是一个正常值,也有可能是另一个Promise实例,表示异步操作的结果可能是另一个异步操作。接下来就举一个执行若干个异步任务的例子.

例2 (若干个异步任务)
function add(input){
  return new Promise(function(resolve,reject){
    var result=input+input;
    console.log("add");
    setTimeout(function(){
      if(result<1000){
        resolve(result);
      }else {
        reject(result);
      }
    },500);
  })
}

function multiply(input){
  return new Promise(function(resolve,reject){
    var result=input*input;
    console.log("multiply");
    setTimeout(resolve,500,result);
  })
}

var p=new Promise(function(resolve,result){
  console.log("start new promise");
  resolve(10);
})

p.then(add).then(multiply).then(add).then(add).then(function(result){
  console.log("小于1000"+result);
},function(result){
  console.log("大于1000"+result);
});
//输出:大于1000,1600

上面这段代码首先定义了add和multiply,然后在add()函数中定义了成功和失败条件,就是当数字小于1000的时候未成功,大于1000的时候为失败。这里最重要的点是add和multiply都会返回一个Promise对象,所以在第一个Promise对象p运行结束后,会开始调用函数add,并将10传递给函数add当作input,此时multiply方法数就会等待add返回的这个新的promise对象发生变化,依次类推,最后的一个then方法指定的回调函数就会等待最后一个add返回的新的promise对象状态发生变化,如果变为Promise对象的状态为成功,就会调用console.log("小于1000"+result);,反之调用console.log("大于1000"+result);

例3 (all和race)

all方法将用于多个Promise实例,包装成一个新的Promise实例。

  var p=Promise.all([p1,p2,p3]);

在使用all的情况下,p的状态由p1,p2,p3决定:

  • 只有p1,p2,p3的状态都变成Resolved,p的状态才会变成Resolved。
  • p1,p2,p3中只要有一个被Rejected,p的状态就变成了Rejected。
  var p1=new Promise(function(resolve,reject){
    setTimeout(resolve,1000,"p1 finish in 1s");
  });

  var p2=new Promise(function(resolve,reject){
    setTimeout(resolve,2000,"p2 finish in 2s");
  });

  Promise.all([p1,p2]).then(function(result){
    console.log("end"+result);
  });
  //直到2s后'p1 finish in 1s'和‘p2 finish in 2s’会被同时输出

上例中,因为只有p1,p2都为Resolved,由它们包装成的新的Promise实例的状态才会变为Resolved,所以直到2秒以后,p1和p2的返回值才会被同时传递给新的promise实例的回调函数。

race方法和all类似,同样将多个Promise实例包装成一个新的Promise实例。

  var p=Promise.race([p1,p2,p3]);

在使用race的情况下,只要p1,p2,p3之中有一个率先改变状态,p的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给p的回调函数。

var p1=new Promise(function(resolve,reject){
  setTimeout(resolve,1000,"p1 finish in 1s");
});

var p2=new Promise(function(resolve,reject){
  setTimeout(resolve,2000,"p2 finish in 2s");
});

Promise.race([p1,p2]).then(function(result){
  console.log("end"+result);
});
//1秒后'p1 finish in 1s'和‘p2 finish in 2s’就会被同时输出

上例中因为p1执行的较快,它的状态会率先变为Resolved,所以p1的返回值,就传递给了新的promise实例的回调函数。

Promise应用于Ajax

使用Promise简化Ajax异步处理:

function ajax(method,url,data){
  var request=new XMLHttpRequest();
  return new Promise(function(resolve,reject{
    request.onreadystatechange=function(){
      if(request.readyState==4){
        if(request.status==200){
          resolve(request.responseText);
        }else {
          reject(request.status);
        }
      }
    });
  request.open(method,url);
  request.send(data);

  })
}

var p=ajax('POST',someUrl);
p.then(function(text){
  alert(text);//成功获取到数据
}).catch(function(status){
  alert(status); //请求数据失败,获得相应代码
});

这里的method可以是'GET'或者'POST',url是php或者asp文件的地址。

总结

关于Promise的大部分知识已经在本文涵盖到,不得不说廖雪峰和阮一峰老师的讲解很全面,但也许有些同学对于ES6并不熟悉,直接看阮一峰的Promise这章会有点吃力,所以笔者尽可能地在他们的基础上解释地更加细致一些,当然,想要全面地了解Promise的所有内容,还是要静下心来阅读ES6入门这本书。这是JS异步的第一篇,接下来仍会介绍别的JS异步的解决方案。

推荐阅读

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容

  • Promiese 简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果,语法上说,Pr...
    雨飞飞雨阅读 3,352评论 0 19
  • Promise的含义:   Promise是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和...
    呼呼哥阅读 2,167评论 0 16
  • 00、前言Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区...
    夜幕小草阅读 2,129评论 0 12
  • //本文内容起初摘抄于 阮一峰 作者的译文,用于记录和学习,建议观者移步于原文 概念: 所谓的Promise,...
    曾经过往阅读 1,231评论 0 7
  • 这个夏天来的娇柔做作却也不失清新自然。晚安~
    Gigi熊阅读 238评论 0 1