微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

ES6快速理解Promise

本篇文章来源于https://mp.weixin.qq.com/s/tetfPizYwMtr-XlBRfZAQA,主要帮助学习者快速理解认识Promise。

Promise是ES6中的特性,现在很多前端框架像AngularJS,Vue等在HTTP请求之后都是返回的Promise处理,因此Promise是必须要掌握的一个知识点。

案例1:

Promise构造函数是同步执行的,promise.then中的函数是异步执行的。

const promise = new Promise((resolve,reject) => {
    console.log(1);
    resolve();
    console.log(2);
});

promise.then(() => {
    console.log(3);
});

console.log(4);


/**运行结果**/
// => 1
// => 2
// => 4
// => 3

案例2:

const first = () => (new Promise((resolve,reject) => {
    console.log(3);
    let p = new Promise((resolve,reject) => {
        console.log(7);
        setTimeout(() => {
            console.log(5);
            resolve(6);
        },0);
        resolve(1);
    });
    resolve(2);
    p.then((arg) => {
        console.log(arg);
    });
}));

first().then((arg) => {
    console.log(arg);
});
console.log(4);


/**打印结果**/
// => 3
// => 7
// => 4
// => 1
// => 2
// => 5

解析:

第一轮事件循环,先执行宏任务,主script,new Promise立即执行,输出3,执行p这个new Promise操作,输出7,发现setTimeout,将回调函数放入下一轮任务队列(Event Quene),p的then,暂时命名为then1,放入微任务队列,且first也由then,命名为then2,放入微任务队列。执行console.log(4),输出4,宏任务执行结束。

再执行微任务,执行then1,输出1,执行thne2,输出3。

第一轮事件循环结束,开始执行第二轮。第二轮事件循环先执行宏任务里面的,也就是setTimeout的回调,输出5。resolve(6)不会生效,因为p的Promise状态一旦改变就不会再变化了。

案例3:

const promise1 = new Promise((resolve,reject) => {
    setTimeout(() => {
        resolve('success');
    },1000);
});

const promise2 = promise1.then(() => {
    throw new Error("error!!!");
});

console.log('promise1',promise1);
console.log('promise2',promise2);

setTimeout(() => {
    console.log('promise1',promise1);
    console.log('promise2',promise2);
},2000);

/**运行结果**/
// => promise1 Promise {<pending>}
// => promise2 Promise {<pending>}
// => Uncaught (in promise) Error: error!!! at <anonymous>
// => promise1 Promise {<resolved>: "success"}
// => promise2 Promise {<rejected>: Error: error!!! at <anonymous>}

解析:

promise有3种状态:pending、fulfilled或rejected。状态改变只能是pending->fulfilled或者pending->rejected,状态一旦改变则不能再变。上面promise2并不是promise1,而是返回的一个新的Promise实例。

案例4:

const promise = new Promise((resolve,reject) => {
    resolve('success1');
    reject('error');
    resolve('success2');
});
promise.then((res) => {
    console.log('then: ',res);
}).catch((err) => {
    console.log('catch: ',err);
});

/**运行结果**/
// => then: success1

解析:

构造函数中的resolve或reject只有第一次执行有效,多次调用没有任何作用,promise状态一旦改变则不能再变。

案例5:

Promise.resolve(1)
    .then((res) => {
        console.log(res);
        return 2;
    })
    .catch((err) => {
        return 3;
    })
    .then((res) => {
        console.log(res);
    });

/**运行结果**/
// => 1
// => 2

解析:

promise可以链式调用。提起链式调用我们通常会想到通过return this实现,不过Promise并不是这样实现的。promise每次调用.then或者.catch都会返回一个新的promise,从而实现了链式调用

案例6:

const promise = new Promise((resolve,reject) => {
    setTimeout(() => {
        console.log('once');
        resolve('success');
    },1000);
});
const start = Date.Now();
promise.then((res) => {
    console.log(res,Date.Now() - start);
});
promise.then((res) => {
    console.log(res,Date.Now() - start);
});

/**运行结果**/
// => once
// => success 1004
// => success 1005

解析:

promise的.then或者.catch可以被调用多次,但是这里Promise构造函数只执行一次。或者说promise内部状态一经改变,并且有了一个值,那么后续每次调用.then或者.catch都会直接拿到该值。

案例7:

Promise.resolve()
    .then(() => {
        return new Error("error!!!");
    })
    .then((res) => {
        console.log('then: ', res);
    })
    .catch((err) => {
        console.log('catch: ',err);
    });

/**运行结果**/
// => then: Error: error!!! at <anonymous>
// 此时:Promise {<fulfilled>: undefined}

解析:

.then或者.catch中return一个error对象并不会抛出错误,所以不会被后续的.catch捕获,需要改成其中一种:

  1.  return Promise.reject(new Error('error!!!'))
  2.  throw new Error('error!!!')

因为返回任意一个非promise的值都会被包裹到promise对象,即return new Error('error!!!')等价于return Promise.resolve(new Error('error!!!'))。

案例8:

const promise = Promise.resolve()
    .then(() => {
        return promise;
    })
promise.catch(console.error);


/**运行结果**/

// =>TypeError: Chaining cycle detected for promise #<Promise>



/**类似于**/
process.nextTick(function tick(){
    console.log("tick");
    process.nextTick(tick);
});

解析:.then或.catch返回的值不能是promise本身,否则会造成死循环。

案例9:

Promise.resolve(1)
    .then(2)
    .then(Promise.resolve(3))
    .then(console.log);

/**运行结果**/
// => 1

解析:.then或者.catch的参数期望是函数,传入非函数则会发生值穿透。

案例10:

Promise.resolve()
    .then(function success(res) {
        throw new Error('error');
    },function fail1(e) {
        console.error('fail1: ',e);
    })
    .catch(function fail2(e) {
        console.error('fail2: ',e);
    });

/**运行结果**/
// => fail2: Error: errpr at success (<anonymous ...>)


/**也等价于**/
Promise.resolve()
    .then(function success1(res) {
        throw new Error('error');
    },function fail1(e) {
        console.error('fail1: ',e);
    })
    .then(function success2(res) {
    },function fail2(e){
        console.error('fail2: ',e);
    });

解析:

.then可以接收两个参数,第一个是处理成功的函数,第二个是处理错误函数

.catch是.then第二个参数的简便写法,但是它们用法上有一点需要注意:.then的第二个处理错误函数捕获不了第一个处理成功的函数抛出的错误,而后续的.catch可以捕获之前的错误

案例11:

process.nextTick(() => {
    console.log('nextTick');
});

Promise.resolve()
    .then(() => {
        console.log('then');
    })
    setImmediate(() => {
        console.log('setImmediate');
    });
comsole.log('end');

/**运行结果**/
// => end
// => nextTick
// => then
// => setImmediate

解析:

process.nextTick和promise.then都属于microtask,而setImmediate属于macrotask,在事件循环的check阶段执行。事件循环的每个阶段(macrotask)之间都会执行microtask,事件循环的开始会先执行一次microtask。

扩展:node.js中 的process.nextTick()setImmediate使用案例。

案例12:

红灯3秒亮一次,绿灯2秒亮一次,黄灯1秒亮一次;如何使用Promise让三个灯不断交替重复亮灯?

function red() {
    console.log("红灯");
}
function green() {
    console.log("绿灯");
}
function yellow() {
    console.log("黄灯");
}

let myLight = (timer,cb) =>{
    return new Promise((resolve) => {
        setTimeout(() => {
            cb();
            resolve();
        },timer);
    });
};

let myStep = () => {
    Promise.resolve().then(() => {
        return myLight(3000,red);
    })
    .then(() => {
        return myLight(2000,green);
    })
    .then(() => {
        return myLight(1000,yellow);
    }).then(()=>{
        myStep();
    });
};
myStep();

解析思路:

换句话说,就是红灯亮起时,承诺2秒后亮绿灯,绿灯亮起时承诺1s秒后亮黄灯,黄灯亮起时,承诺3秒后亮红灯......这显然是一个Promise链式调用,看到这里你心里或许就有思路了,我们需要将我们的每一个亮灯动作写在then()方法中,同时返回一个新的Promise,并将其状态由pending设置为fulfilled,允许下一盏灯亮起。

案例13:

const timeout = ms => new Promise((resolve,reject) => {
    setTimeout(() => {
        resolve();
    },ms);
});

const ajax1 = () => timeout(2000).then(() => {
    console.log("1");
    return 1;
});

const ajax2 = () => timeout(1000).then(() => {
    console.log("2");
    return 2;
});

const ajax3 = () => timeout(2000).then(() => {
    console.log("3");
    return 3;
});

const mergePromise = ajaxArray => {
    // 在这里实现你的代码
    // 保存数组中的函数执行后的结果
    var data = [];

    // Promise.resolve方法调用时不带参数,直接返回一个resolved状态的Promise对象
    var sequence = Promise.resolve();
    ajaxArray.forEach(item => {
        // 第一次的then方法用来执行数组中的每个函数
        // 第二次的then方法接收数组中的函数执行后返回的结果
        // 并把结果添加到data中,然后把data返回
        sequence = sequence.then(item).then(res => {
            data.push(res);
            return data;
        });
    });
    // 遍历结束后,返回一个Promise,也就是sequence,他的[[PromiseValue]]值就是data,
    // 而data(保存数组中的函数执行后的结果)也会作为参数,传入下次调用的then方法中。
    return sequence;
};
mergePromise([ajax1,ajax2,ajax3]).then(data => {
    console.log("done");
    console.log(data); // data为[1,2,3]
});


/**要求分别输出**/
// => 1
// => 2
// => 3
// => done
// => [1,2,3]

案例14:

现有8个图片资源的url,已经存储在数组urls中,且已有一个函数function loading,输入一个url链接,返回一个Promise,该Promise在图片下载完成的时候resolve,下载失败则reject。

要求:任何时刻同时下载的链接数量不可以超过3个。

var urls = ['images/img1.jpg','images/img2.jpg',......]; // 省略了

function loadImg(url){
    return new Promise((resolve,reject) =>{
        const img  = new Image();
        img.onload = () => {
            console.log("一张图片加载完成");
            resolve();
        }
        img.onerror = reject;
        img.src     = url;
    });
}

/**实现**/

function limitLoad(urls,handler,limit) {
    // 对数组做一个拷贝
    const sequence = [...urls];
    let   promises = [];
    // 并发请求到最大数
    promises = sequence.splice(0,limit).map((url,index) => {
        // 这里返回的index是任务在promises的脚标,用于在Promise.race之后找到完成的任务脚标
        return handler(url).then(() => {
            return index;
        });
    });
    // 利用数组的reduce方法来以队列的形式执行
    return sequence.reduce((last,url,currentIndex) => {
        return last.then(() => {
            // 返回最快改变状态的Promise
            return Promise.race(promises);
        }).catch(err =>{
            // 这里的catch不仅用来捕获前面的then方法抛出的错误
            // 更重要的是防止中断整个链式调用
            console.error(err);
        }).then((res) => {
            // 用新的Promise替换掉最快改变状态的Promise
            promises[res] = handler(sequence[currentIndex]).then(()=>{
                return res;
            });
        });
    },Promise.resolve()).then(() =>{
        return Promise.all(promises);
    });
}

limitload(urls,loadImg,3);

// 因为limitLoad函数也返回一个Promise,所以当所有图片加载完成后,可以继续链式调用
limitLoad(urls,loadImg,3).then(() =>{
    console.log("所有图片加载完成");
}).catch(err =>{
    console.log(err);
});

解析:

用Promise来实现就是,先并发请求3个图片资源,这样可以得到3个Promise,组成一个数组promises,然后不断调用Promise.race来返回最快改变形状的Promise,然后从数组promises中删除这个Promise对象,再加入一个新的Promise。直到全部的url被取完,最后再使用Promise.all来处理一遍数组promises中没有改变状态的Promise。

案例15:

封装一个异步加载图片方法

function loadImageAsync(url){
    return new Promise(function(resolve,reject) {
        var image    = new Image();
        image.onload = function(){
            resolve(image);
        };
        image.onerror = function(){
            reject(new Error("Could not load at "+url));
        };
        image.src = url;
    });
}

 

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐