异步的魅力:Promise

当下js最难以处理的的就是异步的任务会什么时候完成和完成之后的回调问题。
而回调函数也是最基础最常用的处理js异步操作的办法,而无限的回调会让代码的可维护性变得非常差。难以掌控的触发状态,让你自己写的代码当时还可以读懂,但是过一段时间如果不重新理一下逻辑,估计自己都会忘了。借用一个例子:

  listen( "click", function handler(evt){
    setTimeout( function request(){
        ajax( "http://some.url.1", function response(text){
            if (text == "hello") {
                handler();
            }
            else if (text == "world") {
                request();
            }
        } );
    }, 1000) ;
} );
doSomething();

这种地狱回调会让代码变得多么不可控。

  • 第一步:执行listern()
  • 第二步: doSomething()
  • 第三步:1000ms后执行ajax()
  • 第四步:ajax完成后,根据结果执行handler或request
    如果handler或request里面还有这样的代码,估计以后维护的同学来改个功能,心里非常的不爽。

在你不知道的javascript一书中,对于回调的信任问题做了阐述 当你使用第三方的库的方法处理回调时很有可能遇到以下信任内容

image.png

基于这些问题,Promise就横空出世了。

一、什么是Promise

Promise是异步编程的一种解决方案,它有三种状态,分别是pending-进行中、resolved-已完成、rejected-已失败

当Promise的状态又pending转变为resolved或rejected时,会执行相应的方法,并且状态一旦改变,就无法再次改变状态,这也是它名字promise-承诺的由来

二、Promise的基本用法

function loadImg(src) {
  let promise = new Promise(function (resolve, reject) {
    // 新建一个img标签
    let img = document.createElement('img')
    // 图片加载成功
    img.onload = function () {
      resolve(img)
    }
    // 图片加载失败
    img.onerror = function () {
      reject('图片加载失败')
     }
    img.src = src
})
return promise
}
let src ='http://img5.imgtn.bdimg.com/it/u=415293130,2419074865&fm=27&gp=0.jpg'
let result = loadImg(src)
result.then((img) => {
  console.log(`加载成功,img宽度=${img.width}`)
}, (text) => {
  console.log(`error:${text}`)
})
  • result把图片地址传入loadImg中,loadImg返回一个Promise对象。
  • 根据图片地址加载图片,加载成功就执行resolve,反之失败就执行reject。

then方法接收两个函数,一个是成功(状态为resolved)的回调函数,一个失败(状态为rejected)的回调函数。

then方法的返回值不是一个promise对象就会被包装成一个promise对象,所以then方法支持链式调用。
再来个按顺序执行的函数:

function taskA() {
  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);

这样看着是不是非常顺眼。

三、Promise.prototype.then() VS Promise.prototype.catch()

.then()方法使Promise原型链上的方法,它包含两个参数方法,分别是已成功resolved的回调和已失败rejected的回调

promise.then(
    () => { console.log('this is success callback') },
    () => { console.log('this is fail callback') }
)

.catch()的作用是捕获Promise的错误,与then()的rejected回调作用几乎一致。但是由于Promise的抛错具有冒泡性质,能够不断传递,这样就能够在下一个catch()中统一处理这些错误。同时catch()也能够捕获then()中抛出的错误,所以建议不要使用then()的rejected回调,而是统一使用catch()来处理错误

promise.then(
    () => { console.log('this is success callback') }
).catch(
    (err) => { console.log(err) }
)

同样,catch()中也可以抛出错误,由于抛出的错误会在下一个catch中被捕获处理,因此可以再添加catch()
使用rejects()方法改变状态和抛出错误 throw new Error() 的作用是相同的

当状态已经改变为resolved后,即使抛出错误,也不会触发then()的错误回调或者catch()方法

then() 和 catch() 都会返回一个新的Promise对象,可以链式调用

promise.then(
    () => { console.log('this is success callback') }
).catch(
    (err) => { console.log(err) }
).then(
    ...
).catch(
    ...
)

Promise实例的异步方法和then()中返回promise有什么区别?

// p1异步方法中返回p2
let p1 = new Promise ( (resolve, reject) => {
    resolve(p2)
} )
let p2 = new Promise ( ... )
// then()中返回promise
let p3 = new Promise ( (resolve, reject) => {
    resolve()
} )
let p4 = new Promise ( ... )
p3.then(
    () => return p4
)

p1异步方法中返回p2

p1的状态取决于p2,如果p2为pending,p1将等待p2状态的改变,p2的状态一旦改变,p1将会立即执行自己对应的回调,即then()中的方法针对的依然是p1

then()中返回promise

由于then()本身就会返回一个新的promise,所以后一个then()针对的永远是一个新的promise,但是像上面代码中我们自己手动返回p4,那么我们就可以在返回的promise中再次通过 resolve() 和 reject() 来改变状态

Promise的其他api
Promise.resolve() / Promise.reject()
用来包装一个现有对象,将其转变为Promise对象,但Promise.resolve()会根据参数情况返回不同的Promise:

参数是Promise:原样返回
参数带有then方法:转换为Promise后立即执行then方法
参数不带then方法、不是对象或没有参数:返回resolved状态的Promise

Promise.reject()会直接返回rejected状态的Promise

Promise.all()
参数为Promise对象数组,如果有不是Promise的对象,将会先通过上面的Promise.resolve()方法转换

var promise = Promise.all( [p1, p2, p3] )
promise.then(
    ...
).catch(
    ...
)

当p1、p2、p3的状态都变成resolved时,promise才会变成resolved,并调用then()的已完成回调,但只要有一个变成rejected状态,promise就会立刻变成rejected状态

Promise.race()
var promise = Promise.race( [p1, p2, p3] )
promise.then(
    ...
).catch(
    ...
)

“竞速”方法,参数与Promise.all()相同,不同的是,参数中的p1、p2、p3只要有一个改变状态,promise就会立刻变成相同的状态并执行对于的回调

Promise.done() / Promise. finally()
Promise.done() 的用法类似 .then() ,可以提供resolved和rejected方法,也可以不提供任何参数,它的主要作用是在回调链的尾端捕捉前面没有被 .catch() 捕捉到的错误

Promise. finally() 接受一个方法作为参数,这个方法不管promise最终的状态是怎样,都一定会被执行

四、大神实现的Promise源码

下面是一个网上找的实现Promise的代码。

const PENDING = 0;
const FULFILLED = 1;
const REJECTED = 2;
class PromiseDemo {
    constructor(callback) {
        this.status = PENDING;
        this.value = null;
        this.defferd = [];
        setTimeout(
            callback.bind(this, this.resolve.bind(this), this.reject.bind(this)
        ), 10);
    };
    resolve(result) {
        this.status = FULFILLED;
        this.value = result;
        this.done();
    }
    reject(error) {
        this.status = REJECTED;
        this.value = error;
    }
    handle(fn) {
        if (!fn) {
            return;
        }
        var value = this.value;
        var t = this.status;
        var p;
        if (t == PENDING) {
            this.defferd.push(fn);
        } else {
            if (t == FULFILLED && typeof fn.onfulfiled == 'function') {
                p = fn.onfulfiled(value);
            }
            if (t == REJECTED && typeof fn.onrejected == 'function') {
                p = fn.onrejected(value);
            }
            var promise = fn.promise;
            if (promise) {
                if (p && p.constructor == this) {
                    p.defferd = promise.defferd;
                } else {
                    p = this;
                    p.defferd = promise.defferd;
                    this.done();
                }
            }
        }
    }
    done() {
        if (this.status == PENDING) {
            return;
        }
        var defferd = this.defferd;
        for (var i = 0; i < defferd.length; i++) {
            this.handle(defferd[i]);
        }
    }
    then(success, fail) {
        var o = {
            onfulfiled: success,
            onrejected: fail
        };
        var status = this.status;
        o.promise = new this.constructor(function () { });
        if (status == PENDING) {
            this.defferd.push(o);
        } else if (status == FULFILLED || status == REJECTED) {
            this.handle(o);
        }
        return o.promise;
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,393评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,790评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,391评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,703评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,613评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,003评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,507评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,158评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,300评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,256评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,274评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,984评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,569评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,662评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,899评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,268评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,840评论 2 339

推荐阅读更多精彩内容