Promise对象是用于异步操作的。

Promise的真正强大之处在于它的多重链式调用,可以避免层层嵌套回调。如果我们在第一次ajax请求后,还要用它返回的结果再次请求呢?

使用Promise,我们就可以利用then进行「链式回调」,将异步操作以同步操作的流程表示出来。

以下是个小Demo:

/* e.g */
sendRequest('test1.html', '').then(function(data1) {
console.log('第一次请求成功, 这是返回的数据:', data1);
return sendRequest('test2.html', data1);
}).then(function(data2) {
console.log('第二次请求成功, 这是返回的数据:', data2);
return sendRequest('test3.html', data2);
}).then(function(data3) {
console.log('第三次请求成功, 这是返回的数据:', data3);
}).catch(function(error) {
//用catch捕捉前面的错误
console.log('sorry, 请求失败了, 这是失败信息:', error);
});

基本用法

上一小节我们认识了promise长什么样,但对它用到的resolve、reject、then、catch想必还不理解。下面我们一步步学习。

Promise对象代表一个未完成、但预计将来会完成的操作。
它有以下三种状态:

  • pending:初始值,不是fulfilled,也不是rejected
  • fulfilled:代表操作成功
  • rejected:代表操作失败

Promise有两种状态改变的方式,既可以从pending转变为fulfilled,也可以从pending转变为rejected。一旦状态改变,就「凝固」了,会一直保持这个状态,不会再发生变化。当状态发生变化,promise.then绑定的函数就会被调用。
注意:Promise一旦新建就会「立即执行」,无法取消。这也是它的缺点之一。
下面就通过例子进一步讲解。

/* 例3.1 */
//构建Promise
var promise = new Promise(function (resolve, reject) {
if (/* 异步操作成功 */) {
resolve(data);
} else {
/* 异步操作失败 */
reject(error);
}
});

类似构建对象,我们使用new来构建一个Promise。Promise接受一个「函数」作为参数,该函数的两个参数分别是resolve和reject。这两个函数就是就是「回调函数」,由JavaScript引擎提供。

resolve函数的作用:在异步操作成功时调用,并将异步操作的结果,作为参数传递出去; 
reject函数的作用:在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

Promise实例生成以后,可以用then方法指定resolved状态和reject状态的回调函数。

/* 接例3.1 */
promise.then(onFulfilled, onRejected); promise.then(function(data) {
// do something when success
}, function(error) {
// do something when failure
});

then方法会返回一个Promise。它有两个参数,分别为Promise从pending变为fulfilled和rejected时的回调函数(第二个参数非必选)。这两个函数都接受Promise对象传出的值作为参数。
简单来说,then就是定义resolve和reject函数的,其resolve参数相当于:

function resolveFun(data) {
//data为promise传出的值
}

而新建Promise中的'resolve(data)',则相当于执行resolveFun函数。
Promise新建后就会立即执行。而then方法中指定的回调函数,将在当前脚本所有同步任务执行完才会执行。如下例:

/* 例3.2 */
var promise = new Promise(function(resolve, reject) {
console.log('before resolved');
resolve();
console.log('after resolved');
}); promise.then(function() {
console.log('resolved');
}); console.log('outer'); -------output-------
before resolved
after resolved
outer
resolved

由于resolve指定的是异步操作成功后的回调函数,它需要等所有同步代码执行后才会执行,因此最后打印'resolved',这个和例2.2是一样的道理。

 

Promise常见问题

经过上一章的学习,相信大家已经学会使用Promise。
总结一下创建promise的流程:

  1. 使用new Promise(fn)或者它的快捷方式Promise.resolve()、Promise.reject(),返回一个promise对象
  2. 在fn中指定异步的处理
    处理结果正常,调用resolve
    处理结果错误,调用reject

如果使用ES6的箭头函数,将会使写法更加简单清晰。

这一章节,将会用例子的形式,以说明promise使用过程中的注意点及容易犯的错误。

情景1:reject 和 catch 的区别

  • promise.then(onFulfilled, onRejected)
    在onFulfilled中发生异常的话,在onRejected中是捕获不到这个异常的。
  • promise.then(onFulfilled).catch(onRejected)
    .then中产生的异常能在.catch中捕获

一般情况,还是建议使用第二种,因为能捕获之前的所有异常。当然了,第二种的.catch()也可以使用.then()表示,它们本质上是没有区别的,.catch === .then(null, onRejected)


情景2:如果在then中抛错,而没有对错误进行处理(即catch),那么会一直保持reject状态,直到catch了错误

/* 例4.1 */
function taskA() {
console.log(x);
console.log("Task A");
}
function taskB() {
console.log("Task B");
}
function onRejected(error) {
console.log("Catch Error: A or B", error);
}
function finalTask() {
console.log("Final Task");
}
var promise = Promise.resolve();
promise
.then(taskA)
.then(taskB)
.catch(onRejected)
.then(finalTask); -------output-------
Catch Error: A or B,ReferenceError: x is not defined
Final Task

根据例4.1的输出结果及流程图,可以看出,A抛错时,会按照 taskA → onRejected → finalTask这个流程来处理。A抛错后,若没有对它进行处理,如例3.7,状态就会维持rejected,taskB不会执行,直到catch了错误。

/* 例4.2 */
function taskA() {
console.log(x);
console.log("Task A");
}
function taskB() {
console.log("Task B");
}
function onRejectedA(error) {
console.log("Catch Error: A", error);
}
function onRejectedB(error) {
console.log("Catch Error: B", error);
}
function finalTask() {
console.log("Final Task");
}
var promise = Promise.resolve();
promise
.then(taskA)
.catch(onRejectedA)
.then(taskB)
.catch(onRejectedB)
.then(finalTask); -------output-------
Catch Error: A ReferenceError: x is not defined
Task B
Final Task

将例4.2与4.1对比,在taskA后多了对A的处理,因此,A抛错时,会按照A会按照 taskA → onRejectedA → taskB → finalTask这个流程来处理,此时taskB是正常执行的。


情景3:每次调用then都会返回一个新创建的promise对象,而then内部只是返回的数据

/* 例4.3 */
//方法1:对同一个promise对象同时调用 then 方法
var p1 = new Promise(function (resolve) {
resolve(100);
});
p1.then(function (value) {
return value * 2;
});
p1.then(function (value) {
return value * 2;
});
p1.then(function (value) {
console.log("finally: " + value);
});
-------output-------
finally: 100 //方法2:对 then 进行 promise chain 方式进行调用
var p2 = new Promise(function (resolve) {
resolve(100);
});
p2.then(function (value) {
return value * 2;
}).then(function (value) {
return value * 2;
}).then(function (value) {
console.log("finally: " + value);
});
-------output-------
finally: 400

第一种方法中,then的调用几乎是同时开始执行的,且传给每个then的value都是100,这种方法应当避免。方法二才是正确的链式调用。
因此容易出现下面的错误写法:

/* 例4.4 */
function badAsyncCall(data) {
var promise = Promise.resolve(data);
promise.then(function(value) {
//do something
return value + 1;
});
return promise;
}
badAsyncCall(10).then(function(value) {
console.log(value); //想要得到11,实际输出10
});
-------output-------
10

正确的写法应该是:

/* 改写例4.4 */
function goodAsyncCall(data) {
var promise = Promise.resolve(data);
return promise.then(function(value) {
//do something
return value + 1;
});
}
goodAsyncCall(10).then(function(value) {
console.log(value);
});
-------output-------
11

情景4:在异步回调中抛错,不会被catch到

// Errors thrown inside asynchronous functions will act like uncaught errors
var promise = new Promise(function(resolve, reject) {
setTimeout(function() {
throw 'Uncaught Exception!';
}, 1000);
}); promise.catch(function(e) {
console.log(e); //This is never called
});

情景5: promise状态变为resove或reject,就凝固了,不会再改变

console.log(1);
new Promise(function (resolve, reject){
reject();
setTimeout(function (){
resolve(); //not called
}, 0);
}).then(function(){
console.log(2);
}, function(){
console.log(3);
});
console.log(4); -------output-------
1
4
3

五 结语



Promise 多重链式调用的更多相关文章

  1. ES6 Promise 的链式调用

    1.什么是Promise Promise 对象代表了未来将要发生的事件,用来传递异步操作的消息. 2.对象的状态不受外界影响.Promise 对象代表一个异步操作,有三种状态: pending: 初始 ...

  2. Promise.then链式调用

    let a = new Promise((resolve,reject)=>{ resolve(1) }).then((r)=>{console.log(r)}).then(()=> ...

  3. 学了ES6,还不会Promise的链式调用?🧐

    前言 本文主要讲解promise的链式调用的方法及其最终方案 应用场景 假如开发有个需求是先要请求到第一个数据,然后根据第一个数据再去请求第二个数据,再根据第二个数据去请求第三个数据...一直到最后得 ...

  4. ES6 Promise对象then方法链式调用

    then()方法的作用是Promise实例添加解决(fulfillment)和拒绝(rejection)状态的回调函数.then()方法会返回一个新的Promise实例,所以then()方法后面可以继 ...

  5. 史上最简单的手写Promise,仅17行代码即可实现Promise链式调用

    Promise的使用相比大家已经孰能生巧了,我这里就不赘述了 先说说我写的Promise的问题吧,无法实现宏任务和微任务里的正确执行(也就是在Promise里面写setTimeout,setInter ...

  6. 使用 jQuery Deferred 和 Promise 创建响应式应用程序

    这篇文章,我们一起探索一下 JavaScript 中的 Deferred 和 Promise 的概念,它们是 JavaScript 工具包(如Dojo和MochiKit)中非常重要的一个功能,最近也首 ...

  7. javascript中你可能遇到的隐式调用

    前言 不知道用隐式调用来形容是否确切,其行为总是隐藏在背后,时不时出来露脸一下,作用貌似不大,但是了解一下还是有用处的,保不准在你的使用下大有作为.所谓的隐式调用简单来说就是自动调用一些方法,而这些方 ...

  8. JavaScript中的链式调用

    链模式 链模式是一种链式调用的方式,准确来说不属于通常定义的设计模式范畴,但链式调用是一种非常有用的代码构建技巧. 描述 链式调用在JavaScript语言中很常见,如jQuery.Promise等, ...

  9. 如何写 JS 的链式调用 ---》JS 设计模式《----方法的链式调用

    1.以$ 函数为例.通常返回一个HTML元素或一个元素集合. 代码如下: function $(){ var elements = []; ;i<arguments.length;i++){ v ...

随机推荐

  1. 《the art of software testing》第四章 测试用例的设计

    白盒测试 逻辑覆盖测试: 逻辑覆盖是以程序内部的逻辑结构为基础的设计测试用例的技术.它属白盒测试.白盒测试的测试方法有代码检查法.静态结构分析法.静态质量度量法.逻辑覆盖法.基本路径测试法.域测试.符 ...

  2. Sql语句摘要

    1.分批更新数据库 declare @x intset @x=1 while(@x<=51) begin begin tran update UserFavorite set UserFavor ...

  3. 编写高质量代码改善C#程序的157个建议——建议5: 使用int?来确保值类型也可以为null

    建议5: 使用int?来确保值类型也可以为null 基元类型为什么需要为null?考虑两个场景: 1)数据库中一个int字段可以被设置为null.在C#中,值被取出来后,为了将它赋值给int类型,不得 ...

  4. Git代码冲突常见解决方法

    在发布这个配置文件的时候,会发生代码冲突: error: Your local changes to the following files would be overwritten by merge ...

  5. Fragment基本介绍

    1.生命周期 onCreateView() :创建视图 onActivityCreate():activity创建完成的时候调用 onDestoryView():销毁视图 onDetach():与ac ...

  6. 从头开始学eShopOnContainers——设置WebSPA单页应用程序

    一.简介 Web SPA单页应用程序需要一些额外的步骤才能使其工作,因为它需要在生成Docker镜像之前构建JavaScript框架依赖项和JS代码. 二.安装基础环境 1.安装NPM 为了能够使用n ...

  7. C# WinForm Webbrowser 强制所有网页链接在同一页面打开或者在TabControl中弹出新窗口(续)

    上面那个文写的如同粑粑一样 效果图 Winfrom 中添加这个类就好了 using System; using System.Collections.Generic; using System.Com ...

  8. 316. Remove Duplicate Letters (accumulate -> count of the difference elements in a vector)

    Given a string which contains only lowercase letters, remove duplicate letters so that every letter ...

  9. 51nod1258 序列求和 V4(伯努利数+多项式求逆)

    题面 传送门 题解 不知道伯努利数是什么的可以先去看看这篇文章 多项式求逆预处理伯努利数就行 因为这里模数感人,所以得用\(MTT\) //minamoto #include<bits/stdc ...

  10. Samba服务为例、简单了解

    先.关掉SElinux.防火墙. ---------------------------- 安装rpm包(主): samba-3.6.9-164.el6.x86_64.rpm 启动检测:samba服务 ...