资讯专栏INFORMATION COLUMN

学习Promise笔记

CntChen / 2950人阅读

摘要:语法该方法是的别名,用于指定发生错误时的回调函数。并把第一个改变状态的的返回值,传给的回调函数。等同于这段代码会让这个对象立即进入状态,并将错误对象传递给指定的回调函数。

什么是Promise?
MDN对Promise的定义:Promise对象用于异步操作,它表示一个尚未完成且预计在未来完成的异步操作。
在学习Promise之前得先了解同步与异步:

JavaScript的执行环境是单线程。所谓单线程,是指JS引擎中负责解释和执行JavaScript代码的线程只有一个,也就是一次只能完成一项任务,这个任务执行完后才能执行下一个,它会阻塞其他任务。这个任务可称为主线程。
但实际上还有其他线程,如事件触发线程,Ajax请求线程等。

同步:

同步模式,即上述所说的单线程模式,一次只能执行一个任务,函数调用后需要等到函数执行结束,返回执行结果,才能进行下一个任务。如果这个任务执行的时间较长,就会导致线程阻塞。

var x = true;
while(x);
console.log("don"t carry out"); // 不会执行

上面代码中的while是一个死循环,它会阻塞进程,因此第三句console不会执行。

异步:

异步模式,即与同步模式相反,可以一起执行多个任务,函数调用后不会立即执行返回执行的结果,如果任务A需要等待,可先执行任务B,等到任务A结果返回后继续回调。
最常见的异步模式就是定时器的使用:

 setTimeout(function() {
    console.log("taskA, asynchronous");
}, 0);
console.log("taskB, synchronize");
//while(true);

-------ouput-------
taskB, synchronize
taskA, asynchronous

虽然定时器延时的时间为0,但taskA还是晚于taskB执行。这是因为定时器是异步的,异步任务会再当前脚本的所有同步任务执行完后才会执行。如果同步代码中含有死循环,则这个异步任务不会执行,因为同步任务阻塞了进程。

回调函数:

上例中,setTimeout里的function便是回调函数。可以理解为:(执行完)回调的函数。
WikiPediacallback的定义可以理解为:回调函数是一段可执行的代码段,它以参数的形式传递给其他代码,在其合适的时间执行这段(回调函数)的代码。回调函数不仅是可以用于异步调用,一般同步的场景也可以用回调。在同步调用下,可能一段时间后执行执行或不执行(未达到执行的条件)

/******************同步回调******************/
var fun1 = function(callback) {
    //do something
    console.log("before callback");
    (callback && typeof(callback) === "function") && callback();
    console.log("after callback");
}
var fun2 = function(param) {
    //do something
    var start = new Date();
    while((new Date() - start) < 3000) { //delay 3s
    }
    console.log("I"m callback");
}
fun1(fun2);

-------output--------
before callback
//after 3s
I’m callback
after callback

由于是同步调用,会阻塞后面的代码,如果fun2是个死循环,后面的代码就不执行了。
除了上面setTimeout为常见的异步回调,另外常见的异步即Ajax请求:

/******************异步回调******************/
function request(url, param, successFun, errorFun) {
    $.ajax({
        type: "GET",
        url: url,
        param: param,
        async: true,    //默认为true,即异步请求;false为同步请求
        success: successFun,
        error: errorFun
    });
}
request("test.html", "", function(data) {
    //请求成功后的回调函数,通常是对请求回来的数据进行处理
    console.log("请求成功啦, 这是返回的数据:", data);
},function(error) {
    console.log("sorry, 请求失败了, 这是失败信息:", error);
});
为什么使用Promise

利用Promise改写上面Ajax的例子:

function sendRequest(url, param) {
    return new Promise(function (resolve, reject) {
        request(url, param, resolve, reject);
    });
}

sendRequest("test.html", "").then(function(data) {
    //异步操作成功后的回调
    console.log("请求成功啦, 这是返回的数据:", data);
}, function(error) {
    //异步操作失败后的回调
    console.log("sorry, 请求失败了, 这是失败信息:", error);
});

Promise的优势在于它的重链式调用,可以避免层层嵌套回调。如果第一次Ajax请求后,还可以用它的返回的结果再次请求

request("test1.html", "", function(data1) {
    console.log("第一次请求成功, 这是返回的数据:", data1);
    request("test2.html", data1, function (data2) {
        console.log("第二次请求成功, 这是返回的数据:", data2);
        request("test3.html", data2, function (data3) {
            console.log("第三次请求成功, 这是返回的数据:", data3);
            //request... 继续请求
        }, function(error3) {
            console.log("第三次请求失败, 这是失败信息:", error3);
        });
    }, function(error2) {
        console.log("第二次请求失败, 这是失败信息:", error2);
    });
}, function(error1) {
    console.log("第一次请求失败, 这是失败信息:", error1);
});

以上出现了多层调用,难以明白层级之间的关系,这就是常说的回调地狱(Pyramid of Doom),而使用Promise,可以利用then进行链式调用,将异步操作以同步操作的流程表示出来。

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

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

pending:初始值,不是fulfilled,也不是rejected

fulfilled:代表操作成功

rejected:代表操作失败

Promise有两种状态改变的方式,既可以从pending转变为fulfilled,也可以从pending转变为rejected。一旦状态改变,就会一直保持这个状态。当状态发生变化,Promise.then绑定的函数就会被调用。

注意:Promise一旦新建就会立即执行,无法取消。这也是它的缺点之一。
下面通过一个例子进一步讲解:

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

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

resolve函数的作用:在异步操作成功时调用,并将异步操作的结果,作为参会素传递出去;

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

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

promise.then(onFulfilled, onRejected);

promise.then(function(data) {
  // do something when success
}, function(error) {
  // do something when failure
});

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

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

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

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’

基本API
.then()
语法:Promise.prototype.then(onFulfilled,onRejected)

Promise添加onFulfilledonRejected回调,并返回的是一个新的Promise实例(不是原来那个Promise实例),且返回值将作为参数传入这个新Promiseresolve函数。
因此可以使用链式写法。由于前一个回调函数,返回的还是一个Promise对象(即有异步操作),这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用。

.catch()
语法:Promise.prototype.catch(onRejected)

该方法是.then(undefined,onRejected)的别名,用于指定发生错误时的回调函数。

promise.then(function(data) {
    console.log("success");
}).catch(function(error) {
    console.log("error", error);
});

/*******等同于*******/
promise.then(function(data) {
    console.log("success");
}).then(undefined, function(error) {
    console.log("error", error);
});
var promise = new Promise(function (resolve, reject) {
    throw new Error("test");
});
/*******等同于*******/
var promise = new Promise(function (resolve, reject) {
    reject(new Error("test"));
});

//用catch捕获
promise.catch(function (error) {
    console.log(error);
});
-------output-------
Error: test

从上例可知,reject方法的作用等同于抛错。
promise对象的错误,会一直向后传递,直到被捕获。即错误总会被下一个catch所捕获。then方法指定的回调函数,若抛出错误,也会被下一个catch捕获。catch中也能抛错,则需要后面的catch来捕获。

sendRequest("test.html").then(function(data1) {
    //do something
}).then(function (data2) {
    //do something
}).catch(function (error) {
    //处理前面三个Promise产生的错误
});

上面提到的,promise状态一旦改变就会凝固,不会再改变。因此promise一旦fulfilled了,再抛错,也不会变为rejected,就不会被catch

var promise = new Promise(function(resolve, reject) {
  resolve();
  throw "error";
});

promise.catch(function(e) {
   console.log(e);      //This is never called
});

如果没有使用catch方法指定处理错误的回调函数,Promise对象抛出的错误不会传递到外层代码,即不会有任何反应(Chrome会抛错),这是Promise的另一个缺点。

var promise = new Promise(function (resolve, reject) {
    resolve(x);
});
promise.then(function (data) {
    console.log(data);
});




如图所示,只有Chrome会抛错,且promise状态变为rejectedFirefoxSafari中错误不会被捕获,也不会传递到外层代码,最后没有任何输出,promise状态也变为rejected

.all()
语法:Promise.all(iterable)

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

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

Promise.all方法接受一个数组(或具有Iterator接口)作参数,数组中的对象(p1p2p3)均为promise实例(如果不是一个promise,该项会被用Promise.resolve转换为一个promise)。它的状态由这三个promise实例来决定。

p1p2p3状态都为fulfilledp的状态才会变为fulfilled,并将三个promise返回的结果,按参数的顺序(而不是resolved的顺序)存入数组,传给p的回调函数;

/* 例3.8 */
var p1 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 3000, "first");
});
var p2 = new Promise(function (resolve, reject) {
    resolve("second");
});
var p3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 1000, "third");
}); 

Promise.all([p1, p2, p3]).then(function(values) { 
  console.log(values); 
});

-------output-------
//约 3s 后
["first", "second", "third"] 

p1p2p3其中之一状态变为rejectedp的状态也会变为rejected,并把第一个被rejectedpromise的返回值,传给p的回调函数;

var p1 = new Promise((resolve, reject) => { 
  setTimeout(resolve, 1000, "one"); 
}); 
var p2 = new Promise((resolve, reject) => { 
  setTimeout(reject, 2000, "two"); 
});
var p3 = new Promise((resolve, reject) => {
  reject("three");
});

Promise.all([p1, p2, p3]).then(function (value) {
    console.log("resolve", value);
}, function (error) {
    console.log("reject", error);    // => reject three
});

-------output-------
reject three

这多个promise是同时开始、并行执行的,而不是顺序执行的。

function timerPromisefy(delay) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            resolve(delay);
        }, delay);
    });
}
var startDate = Date.now();

Promise.all([
    timerPromisefy(1),
    timerPromisefy(32),
    timerPromisefy(64),
    timerPromisefy(128)
]).then(function (values) {
    console.log(Date.now() - startDate + "ms");
    console.log(values);
});
-------output-------
133ms       //不一定,但大于128ms
[1,32,64,128]
.race()
语法:Promise.race(iterable)

该方法同样接受一个数组(或具有Iterator接口)作参数。当p1p2p3中有一个实例的状态发生改变(变为fulfilledrejected),p的状态就跟着改变。并把第一个改变状态的promise的返回值,传给p的回调函数。

var p1 = new Promise(function(resolve, reject) { 
    setTimeout(reject, 500, "one"); 
});
var p2 = new Promise(function(resolve, reject) { 
    setTimeout(resolve, 100, "two"); 
});

Promise.race([p1, p2]).then(function(value) {
    console.log("resolve", value); 
}, function(error) {
    //not called
    console.log("reject", error); 
});
-------output-------
resolve two

var p3 = new Promise(function(resolve, reject) { 
    setTimeout(resolve, 500, "three");
});
var p4 = new Promise(function(resolve, reject) { 
    setTimeout(reject, 100, "four"); 
});

Promise.race([p3, p4]).then(function(value) {
    //not called
    console.log("resolve", value);              
}, function(error) {
    console.log("reject", error); 
});
-------output-------
reject four
在第一个promise对象变为resolve后,并不会取消其他promise对象的执行,如下
var fastPromise = new Promise(function (resolve) {
    setTimeout(function () {
        console.log("fastPromise");
        resolve("resolve fastPromise");
    }, 100);
});
var slowPromise = new Promise(function (resolve) {
    setTimeout(function () {
        console.log("slowPromise");
        resolve("resolve slowPromise");
    }, 1000);
});
// 第一个promise变为resolve后程序停止
Promise.race([fastPromise, slowPromise]).then(function (value) {
    console.log(value);    // => resolve fastPromise
});
-------output-------
fastPromise
resolve fastPromise
slowPromise     //仍会执行
.resolve()
语法
Promise.resolve(value);
Promise.resolve(promise);
Promise.resolve(thenable);

它可以看做new Promise()的快捷方式

Promise.resolve("Success");

/*******等同于*******/
new Promise(function (resolve) {
    resolve("Success");
});

这段代码会让这个Promise对象立即进入resolved状态,并将结果success传递给then指定的onFulfilled回调函数。由于Promise.resolve()也是返回Promise对象,因此可以用.then()处理其返回值。

Promise.resolve("success").then(function (value) {
    console.log(value);
});
-------output-------
success
//Resolving an array
Promise.resolve([1,2,3]).then(function(value) {
  console.log(value[0]);    // => 1
});

//Resolving a Promise
var p1 = Promise.resolve("this is p1");
var p2 = Promise.resolve(p1);
p2.then(function (value) {
    console.log(value);     // => this is p1
});

Promise.resolve()的另一个作用就是将thenable对象(即带有then的对象)转换为promise对象。

var p1 = Promise.resolve({ 
    then: function (resolve, reject) { 
        resolve("this is an thenable object!");
    }
});
console.log(p1 instanceof Promise);     // => true

p1.then(function(value) {
    console.log(value);     // => this is an thenable object!
  }, function(e) {
    //not called
});

下面两个例子,无论是在什么时候抛异常,只要promise状态变成resolvedrejected,状态不会再改变,这和新建promise是一样的。

//在回调函数前抛异常
var p1 = { 
    then: function(resolve) {
      throw new Error("error");
      resolve("Resolved");
    }
};

var p2 = Promise.resolve(p1);
p2.then(function(value) {
    //not called
}, function(error) {
    console.log(error);       // => Error: error
});

//在回调函数后抛异常
var p3 = { 
    then: function(resolve) {
        resolve("Resolved");
        throw new Error("error");
    }
};

var p4 = Promise.resolve(p3);
p4.then(function(value) {
    console.log(value);     // => Resolved
}, function(error) {
    //not called
});
.reject()
语法:Promise.reject(reason)

这个方法和上述的Promise.resolve()类似,它也是new Promise()的快捷方式。

Promise.reject(new Error("error"));

/*******等同于*******/
new Promise(function (resolve, reject) {
    reject(new Error("error"));
});

这段代码会让这个Promise对象立即进入rejected状态,并将错误对象传递给then指定的onRejected回调函数。

Promise常见问题 总结一下创建promise的流程:

使用new Promise(fn)或者它的快捷方式Promise.resolve()、Promise.reject(),返回一个promise对象

在fn中指定异步的处理

处理结果正常,调用resolve

处理结果错误,调用reject

如果使用ES6的箭头函数,将会使写法更加简单清晰
接下来用例子说明promise使用过程中的注意点及容易犯的错误。

情景1:reject和catch的区别

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

promise.then(onFilfilled).catch(onRejected) .then中产生的异常能在.catch中捕获。

一般情况,使用第二种,第二种的.catch()也可以使用.then()表示,它们本质上没有区别,.catch===.then(null,onRejected)

情景2:

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

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

从代码的输出结果及流程,可以看出,A抛错时,会按照taskA->onRejected->finalTask这个流程来处理。A抛错后,若没有对它进行处理,状态就会维持rejectedtaskB不会执行,直到catch了错误。

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

TaskA后多了对A的处理,因此,A抛错时,会按照taskA->onRejectedA->taskB->finalTask这个流程来处理,此时taskB是正常执行的。

情景3:

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

//方法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的调用几乎是同时开始执行的,且传给每个thenvalue都是100,这种方法应当避免。方法二才是正确的链式调用。
容易出现下面的错误写法:

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

正确的写法:

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状态变为resolvereject,就凝固了,不会再改变

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/101106.html

相关文章

  • Promise学习笔记(二):规范

    摘要:下一篇大概就是源码方面的学习笔记了龟速学习中这一次我是去看了下规范照例传送门图灵社区规范首先吧个人总结下该用的词解决结婚拒绝婉拒终值值传家宝拒因好人卡等等异常车祸理下概念我们的的就像是一场姻缘对吧解决呢就是结婚成功啦传家宝也如愿的传给下一代 下一篇大概就是源码方面的学习笔记了...龟速学习中... 这一次我是去看了下Promises/A+规范照例传送门:图灵社区Promises/A+规...

    _Suqin 评论0 收藏0
  • 重学前端学习笔记(十七)--Promise里的代码为什么比setTimeout先执行?

    摘要:版本以及之前,本身还没有异步执行代码的能力,宿主环境传递给引擎,然后按顺序执行,由宿主发起任务。采纳引擎术语,把宿主发起的任务称为宏观任务,把引擎发起的任务称为微观任务。基本用法示例的回调是一个异步的执行过程。 笔记说明 重学前端是程劭非(winter)【前手机淘宝前端负责人】在极客时间开的一个专栏,每天10分钟,重构你的前端知识体系,笔者主要整理学习过程的一些要点笔记以及感悟,完整的...

    pinecone 评论0 收藏0
  • 重学前端学习笔记(十七)--Promise里的代码为什么比setTimeout先执行?

    摘要:版本以及之前,本身还没有异步执行代码的能力,宿主环境传递给引擎,然后按顺序执行,由宿主发起任务。采纳引擎术语,把宿主发起的任务称为宏观任务,把引擎发起的任务称为微观任务。基本用法示例的回调是一个异步的执行过程。 笔记说明 重学前端是程劭非(winter)【前手机淘宝前端负责人】在极客时间开的一个专栏,每天10分钟,重构你的前端知识体系,笔者主要整理学习过程的一些要点笔记以及感悟,完整的...

    zorpan 评论0 收藏0
  • 重学前端学习笔记(十七)--Promise里的代码为什么比setTimeout先执行?

    摘要:版本以及之前,本身还没有异步执行代码的能力,宿主环境传递给引擎,然后按顺序执行,由宿主发起任务。采纳引擎术语,把宿主发起的任务称为宏观任务,把引擎发起的任务称为微观任务。基本用法示例的回调是一个异步的执行过程。 笔记说明 重学前端是程劭非(winter)【前手机淘宝前端负责人】在极客时间开的一个专栏,每天10分钟,重构你的前端知识体系,笔者主要整理学习过程的一些要点笔记以及感悟,完整的...

    xiongzenghui 评论0 收藏0
  • Promise学习笔记

    摘要:异步操作未完成异步操作成功异步操作失败基本用法是一个构造函数,接收一个参数,这个参数是函数,同时这个参数函数要传入两个参数,,分别表示异步操作执行成功后的回调函数和异步操作执行失败后的回调函数。如果调用函数,就会调用方法的第一个参数。 Promise对象 Promise 表示一个异步操作的最终结果,与之进行交互的方式主要是 then 方法,该方法注册了两个回调函数,用于接收 promi...

    tigerZH 评论0 收藏0

发表评论

0条评论

CntChen

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<